tapioca 0.4.1 → 0.4.2

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: 91a976cddc1429c3b08568b39d8fae5fe8d4d54a8b8b2b04deebb75f475987e8
4
- data.tar.gz: 45074066696bd5c838ab09523980fd9c148faeb1b9f6ee800e8752e636edbd1a
3
+ metadata.gz: 46bdcc628673ddc597495e267b471e3a6f4c705b8f8d8d32dffba17c69470c21
4
+ data.tar.gz: 4a2e597d03ea9354835e73f52034d088b1c15ac65f445d0f6963bbbca09c2b23
5
5
  SHA512:
6
- metadata.gz: 1646cf3ec4957442bf0895a636bfc684e00f0d88e2252502d119fd3f4b4073e41b773ce0cbe8f725766d0c82bae7f67b92381a32a123b6fa2067ddd6a9e2c8d2
7
- data.tar.gz: 33f8dae8437b44c2bb2e7d4a1bb483adc11c0ae58322b62fa97a879698ba3d9abab4f198d38b7a8433e57f32f64df5b39ce1b5d8a13ebe37b011dae2d179d926
6
+ metadata.gz: 51c134a7948c262721672f61105e08e1e31146b70d982fac0944b0d42be91d5e2210a110775596d11704e60eac68d6b2e27c016587777c658fab2b64131f7a1e
7
+ data.tar.gz: 1f048066d59bc2cc08a62fb93de96afdb7146d8e1c987c2a98677edd4920b08896096f9beaa658466ebd54be729c2bf6947ef1c409ab7329dac5ac27f7847dbc
data/README.md CHANGED
@@ -8,6 +8,8 @@ As yet, no gem exports type information in a consumable format and it would be a
8
8
 
9
9
  When you run `tapioca sync` in a project, `tapioca` loads all the gems that are in your dependency list from the Gemfile into memory. It then performs runtime introspection on the loaded types to understand their structure and generates an appropriate RBI file for each gem with a versioned filename.
10
10
 
11
+ ## Manual gem requires
12
+
11
13
  For gems that have a normal default `require` and load all of their constants through such a require, everything works seamlessly. However, for gems that are marked as `require: false` in the Gemfile, or for gems that export optionally loaded types via different requires, where a single require does not load the whole gem code into memory, `tapioca` will not be able to load some of the types into memory and, thus, won't be able to generate complete RBIs for them. For this reason, we need to keep a small external file named `sorbet/tapioca/require.rb` that is executed after all the gems in the Gemfile have been required and before generation of gem RBIs have started. This file is responsible for adding the requires for additional files from gems, which are not covered by the default require.
12
14
 
13
15
  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:
@@ -349,9 +349,9 @@ module Tapioca
349
349
  !(constant.singleton_class < Object.const_get(:StrongTypeGeneration))
350
350
  end
351
351
 
352
- sig { params(column_type: Module).returns(String) }
352
+ sig { params(column_type: Object).returns(String) }
353
353
  def handle_unknown_type(column_type)
354
- return "T.untyped" unless column_type < ActiveModel::Type::Value
354
+ return "T.untyped" unless ActiveModel::Type::Value === column_type
355
355
 
356
356
  lookup_return_type_of_method(column_type, :deserialize) ||
357
357
  lookup_return_type_of_method(column_type, :cast) ||
@@ -359,19 +359,27 @@ module Tapioca
359
359
  "T.untyped"
360
360
  end
361
361
 
362
- sig { params(column_type: Module, method: Symbol).returns(T.nilable(String)) }
362
+ sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
363
363
  def lookup_return_type_of_method(column_type, method)
364
- signature = T::Private::Methods.signature_for_method(column_type.instance_method(method))
364
+ signature = T::Private::Methods.signature_for_method(column_type.method(method))
365
365
  return unless signature
366
366
 
367
- return_type = signature.return_type.to_s
368
- return_type if return_type != "<VOID>" && return_type != "<NOT-TYPED>"
367
+ return_type = signature.return_type
368
+ return if T::Types::Simple === return_type && T::Generic === return_type.raw_type
369
+ return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
370
+
371
+ return_type.to_s
369
372
  end
370
373
 
371
- sig { params(column_type: Module, method: Symbol).returns(T.nilable(String)) }
374
+ sig { params(column_type: ActiveModel::Type::Value, method: Symbol).returns(T.nilable(String)) }
372
375
  def lookup_arg_type_of_method(column_type, method)
373
- signature = T::Private::Methods.signature_for_method(column_type.instance_method(method))
374
- signature.arg_types.first.last.to_s if signature
376
+ signature = T::Private::Methods.signature_for_method(column_type.method(method))
377
+ return unless signature
378
+
379
+ arg_type = signature.arg_types.first.last
380
+ return if T::Types::Simple === arg_type && T::Generic === arg_type.raw_type
381
+
382
+ arg_type.to_s
375
383
  end
376
384
  end
377
385
  end
@@ -1,6 +1,8 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "parlour"
5
+
4
6
  module Tapioca
5
7
  module Compilers
6
8
  module Dsl
@@ -29,7 +29,7 @@ module Tapioca
29
29
  def generate
30
30
  symbols
31
31
  .sort
32
- .map(&method(:generate_from_symbol))
32
+ .map { |symbol| generate_from_symbol(symbol) }
33
33
  .compact
34
34
  .join("\n\n")
35
35
  .concat("\n")
@@ -28,7 +28,7 @@ module Tapioca
28
28
  sig { returns(T::Array[Gem]) }
29
29
  def dependencies
30
30
  @dependencies ||= begin
31
- specs = definition.specs.to_a
31
+ specs = definition.locked_gems.specs.to_a
32
32
 
33
33
  definition
34
34
  .resolve
@@ -79,17 +79,18 @@ module Tapioca
79
79
  extend(T::Sig)
80
80
 
81
81
  IGNORED_GEMS = T.let(%w{
82
- sorbet sorbet-static sorbet-runtime tapioca
82
+ sorbet sorbet-static sorbet-runtime
83
83
  }.freeze, T::Array[String])
84
84
 
85
85
  sig { returns(String) }
86
- attr_reader :full_gem_path
86
+ attr_reader :full_gem_path, :version
87
87
 
88
88
  sig { params(spec: Spec).void }
89
89
  def initialize(spec)
90
90
  @spec = T.let(spec, Tapioca::Gemfile::Spec)
91
91
  real_gem_path = to_realpath(@spec.full_gem_path)
92
92
  @full_gem_path = T.let(real_gem_path, String)
93
+ @version = T.let(version_string, String)
93
94
  end
94
95
 
95
96
  sig { params(gemfile_dir: String).returns(T::Boolean) }
@@ -109,11 +110,6 @@ module Tapioca
109
110
  @spec.name
110
111
  end
111
112
 
112
- sig { returns(::Gem::Version) }
113
- def version
114
- @spec.version
115
- end
116
-
117
113
  sig { returns(String) }
118
114
  def rbi_file_name
119
115
  "#{name}@#{version}.rbi"
@@ -121,11 +117,38 @@ module Tapioca
121
117
 
122
118
  sig { params(path: String).returns(T::Boolean) }
123
119
  def contains_path?(path)
124
- to_realpath(path).start_with?(full_gem_path)
120
+ to_realpath(path).start_with?(full_gem_path) || has_parent_gemspec?(path)
125
121
  end
126
122
 
127
123
  private
128
124
 
125
+ sig { returns(String) }
126
+ def version_string
127
+ version = @spec.version.to_s
128
+ version += "-#{@spec.source.revision}" if Bundler::Source::Git === @spec.source
129
+ version
130
+ end
131
+
132
+ sig { params(path: String).returns(T::Boolean) }
133
+ def has_parent_gemspec?(path)
134
+ # For some Git installed gems the location of the loaded file can
135
+ # be different from the gem path as indicated by the spec file
136
+ #
137
+ # To compensate for these cases, we walk up the directory hierarchy
138
+ # from the given file and try to match a <gem-name.gemspec> file in
139
+ # one of those folders to see if the path really belongs in the given gem
140
+ # or not.
141
+ return false unless Bundler::Source::Git === @spec.source
142
+ parent = Pathname.new(path)
143
+
144
+ until parent.root?
145
+ parent = parent.parent.expand_path
146
+ return true if parent.join("#{name}.gemspec").file?
147
+ end
148
+
149
+ false
150
+ end
151
+
129
152
  sig { params(path: T.any(String, Pathname)).returns(String) }
130
153
  def to_realpath(path)
131
154
  path_string = path.to_s
@@ -162,6 +162,11 @@ module Tapioca
162
162
 
163
163
  private
164
164
 
165
+ EMPTY_RBI_COMMENT = <<~CONTENT
166
+ # THIS IS AN EMPTY RBI FILE.
167
+ # see https://github.com/Shopify/tapioca/blob/master/README.md#manual-gem-requires
168
+ CONTENT
169
+
165
170
  sig { returns(Gemfile) }
166
171
  def bundle
167
172
  @bundle ||= Gemfile.new
@@ -422,20 +427,25 @@ module Tapioca
422
427
  say("Compiling #{gem_name}, this may take a few seconds... ")
423
428
 
424
429
  strictness = config.typed_overrides[gem.name] || "true"
425
-
430
+ rbi_body_content = compiler.compile(gem)
426
431
  content = String.new
427
432
  content << rbi_header(
428
433
  config.generate_command,
429
434
  reason: "types exported from the `#{gem.name}` gem",
430
435
  strictness: strictness
431
436
  )
432
- content << compiler.compile(gem)
433
437
 
434
438
  FileUtils.mkdir_p(config.outdir)
435
439
  filename = config.outpath / gem.rbi_file_name
436
- File.write(filename.to_s, content)
437
440
 
438
- say("Done", :green)
441
+ if rbi_body_content.strip.empty?
442
+ content << EMPTY_RBI_COMMENT
443
+ say("Done (empty output)", :yellow)
444
+ else
445
+ content << rbi_body_content
446
+ say("Done", :green)
447
+ end
448
+ File.write(filename.to_s, content)
439
449
 
440
450
  Pathname.glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
441
451
  remove(file) unless file.basename.to_s == gem.rbi_file_name
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.4.1"
5
+ VERSION = "0.4.2"
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.1
4
+ version: 0.4.2
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-07-27 00:00:00.000000000 Z
14
+ date: 2020-08-18 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: pry