tapioca 0.14.4 → 0.15.1

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: 6f75efebcf13cb6839056d8869fe4d2d6e2051605a90edf517904d8a14a33913
4
- data.tar.gz: 41fcfa1d3da8c919577146cbbc6a92ad07a62b9e7ac077642ac78290ffb2ecc2
3
+ metadata.gz: 12203d783fb506aff6b357dc3dc02fd7f9d9e7147fd2fb715329a5311ff96bf6
4
+ data.tar.gz: 0ac885bad578baec1701f6b2f46e48ca5f12e4c5d8fd9840d63195291400a430
5
5
  SHA512:
6
- metadata.gz: ea78667fe6cf4f54a0b4dd004de2ab465548a231aa2b35d52de83d89e43a0cbd0b21989e249c90415f41dfb5ac8c5082b9a84575bc4eed83418eab9a4a4f33b1
7
- data.tar.gz: 05ea62993f5983a3fb69285f13a07678ddc912e5b7aa5657e81e02906747ab19f17d63a26141deb764d217827ba8097643e3797acf98c31f2323390a52780659
6
+ metadata.gz: f42230c7ca6b96bb6ed299faa2b4c8d8f87ae44244a6501006c55b22c6307b9a0b777cdbb8d713f6ee3320d44502428c17ddf86dd85929be842433557d3e3793
7
+ data.tar.gz: df6f57d7c1446e70bd759bc84c5fa06f5985e01783be8d8f7655e06e3045c5de17cc1e931d4f0b5d78bc19f867de61dc228c77eb2f4df325038e09d908e2efd0
@@ -46,22 +46,22 @@ module Tapioca
46
46
  GitAttributes.create_vendored_attribute_file(@outpath)
47
47
  end
48
48
 
49
- sig { returns(T::Array[String]) }
49
+ sig { returns(T::Array[GemInfo]) }
50
50
  def list_gemfile_gems
51
51
  say("Listing gems from Gemfile.lock... ", [:blue, :bold])
52
52
  gemfile = Bundler.read_file("Gemfile.lock")
53
53
  parser = Bundler::LockfileParser.new(gemfile)
54
- gem_names = parser.specs.map(&:name)
54
+ gem_info = parser.specs.map { |spec| GemInfo.from_spec(spec) }
55
55
  say("Done", :green)
56
- gem_names
56
+ gem_info
57
57
  end
58
58
 
59
- sig { params(project_gems: T::Array[String]).void }
59
+ sig { params(project_gems: T::Array[GemInfo]).void }
60
60
  def remove_expired_annotations(project_gems)
61
61
  say("Removing annotations for gems that have been removed... ", [:blue, :bold])
62
62
 
63
63
  annotations = Pathname.glob(@outpath.join("*.rbi")).map { |f| f.basename(".*").to_s }
64
- expired = annotations - project_gems
64
+ expired = annotations - project_gems.map(&:name)
65
65
 
66
66
  if expired.empty?
67
67
  say(" Nothing to do")
@@ -109,14 +109,14 @@ module Tapioca
109
109
  index
110
110
  end
111
111
 
112
- sig { params(gem_names: T::Array[String]).returns(T::Array[String]) }
113
- def fetch_annotations(gem_names)
112
+ sig { params(project_gems: T::Array[GemInfo]).returns(T::Array[String]) }
113
+ def fetch_annotations(project_gems)
114
114
  say("Fetching gem annotations from central repository... ", [:blue, :bold])
115
- fetchable_gems = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
115
+ fetchable_gems = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[GemInfo, T::Array[String]])
116
116
 
117
- gem_names.each_with_object(fetchable_gems) do |gem_name, hash|
117
+ project_gems.each_with_object(fetchable_gems) do |gem_info, hash|
118
118
  @indexes.each do |uri, index|
119
- T.must(hash[gem_name]) << uri if index.has_gem?(gem_name)
119
+ T.must(hash[gem_info]) << uri if index.has_gem?(gem_info.name)
120
120
  end
121
121
  end
122
122
 
@@ -127,13 +127,16 @@ module Tapioca
127
127
  end
128
128
 
129
129
  say("\n")
130
- fetched_gems = fetchable_gems.select { |gem_name, repo_uris| fetch_annotation(repo_uris, gem_name) }
130
+ fetched_gems = fetchable_gems.select { |gem_info, repo_uris| fetch_annotation(repo_uris, gem_info) }
131
131
  say("\nDone", :green)
132
- fetched_gems.keys.sort
132
+ fetched_gems.keys.map(&:name).sort
133
133
  end
134
134
 
135
- sig { params(repo_uris: T::Array[String], gem_name: String).void }
136
- def fetch_annotation(repo_uris, gem_name)
135
+ sig { params(repo_uris: T::Array[String], gem_info: GemInfo).void }
136
+ def fetch_annotation(repo_uris, gem_info)
137
+ gem_name = gem_info.name
138
+ gem_version = gem_info.version
139
+
137
140
  contents = repo_uris.map do |repo_uri|
138
141
  fetch_file(repo_uri, "#{CENTRAL_REPO_ANNOTATIONS_DIR}/#{gem_name}.rbi")
139
142
  end
@@ -142,6 +145,7 @@ module Tapioca
142
145
  return unless content
143
146
 
144
147
  content = apply_typed_override(gem_name, content)
148
+ content = filter_versions(gem_version, content)
145
149
  content = add_header(gem_name, content)
146
150
 
147
151
  say("\n Fetched #{set_color(gem_name, :yellow, :bold)}", :green)
@@ -221,6 +225,14 @@ module Tapioca
221
225
  Spoom::Sorbet::Sigils.update_sigil(content, strictness)
222
226
  end
223
227
 
228
+ sig { params(gem_version: ::Gem::Version, content: String).returns(String) }
229
+ def filter_versions(gem_version, content)
230
+ rbi = RBI::Parser.parse_string(content)
231
+ rbi.filter_versions!(gem_version)
232
+
233
+ rbi.string
234
+ end
235
+
224
236
  sig { params(gem_name: String, contents: T::Array[String]).returns(T.nilable(String)) }
225
237
  def merge_files(gem_name, contents)
226
238
  return if contents.empty?
@@ -147,7 +147,7 @@ module Tapioca
147
147
  machine.create_method(
148
148
  method,
149
149
  parameters: [
150
- create_opt_param("symbol", type: "T.nilable(Symbol)", default: "nil"),
150
+ create_rest_param("callbacks", type: "T.any(String, Symbol, T::Class[T.anything], Proc)"),
151
151
  create_block_param("block", type: "T.nilable(T.proc.bind(#{constant_name}).void)"),
152
152
  ],
153
153
  )
@@ -190,7 +190,7 @@ module Tapioca
190
190
  # Grab all Spawn methods
191
191
  query_methods |= ActiveRecord::SpawnMethods.instance_methods(false)
192
192
  # Remove the ones we know are private API
193
- query_methods -= [:arel, :build_subquery, :construct_join_dependency, :extensions, :spawn]
193
+ query_methods -= [:all, :arel, :build_subquery, :construct_join_dependency, :extensions, :spawn]
194
194
  # Remove "group" which needs a custom return type for GroupChains
195
195
  query_methods -= [:group]
196
196
  # Remove "where" which needs a custom return type for WhereChains
@@ -231,7 +231,7 @@ module Tapioca
231
231
  "fetch_#{suffix}",
232
232
  class_method: true,
233
233
  parameters: parameters,
234
- return_type: type,
234
+ return_type: field.unique ? type : COLLECTION_TYPE.call(type),
235
235
  )
236
236
 
237
237
  klass.create_method(
@@ -104,7 +104,7 @@ module Tapioca
104
104
 
105
105
  column = @constant.columns_hash[column_name]
106
106
  column_type = @constant.attribute_types[column_name]
107
- getter_type = type_for_activerecord_value(column_type)
107
+ getter_type = type_for_activerecord_value(column_type, column_nullability: !!column&.null)
108
108
  setter_type =
109
109
  case column_type
110
110
  when ActiveRecord::Enum::EnumType
@@ -121,8 +121,8 @@ module Tapioca
121
121
  end
122
122
  end
123
123
 
124
- sig { params(column_type: T.untyped).returns(String) }
125
- def type_for_activerecord_value(column_type)
124
+ sig { params(column_type: T.untyped, column_nullability: T::Boolean).returns(String) }
125
+ def type_for_activerecord_value(column_type, column_nullability:)
126
126
  case column_type
127
127
  when ->(type) { defined?(MoneyColumn) && MoneyColumn::ActiveRecordType === type }
128
128
  "::Money"
@@ -133,11 +133,12 @@ module Tapioca
133
133
  }
134
134
  # Reflect to see if `ActiveModel::Type::Value` is being used first.
135
135
  getter_type = Tapioca::Dsl::Helpers::ActiveModelTypeHelper.type_for(column_type)
136
- return getter_type unless getter_type == "T.untyped"
137
136
 
138
- # Otherwise fallback to String as `ActiveRecord::Encryption::EncryptedAttributeType` inherits from
137
+ # Fallback to String as `ActiveRecord::Encryption::EncryptedAttributeType` inherits from
139
138
  # `ActiveRecord::Type::Text` which inherits from `ActiveModel::Type::String`.
140
- "::String"
139
+ return "::String" if getter_type == "T.untyped"
140
+
141
+ as_non_nilable_if_persisted_and_not_nullable(getter_type, column_nullability:)
141
142
  when ActiveRecord::Type::String
142
143
  "::String"
143
144
  when ActiveRecord::Type::Date
@@ -160,7 +161,7 @@ module Tapioca
160
161
  defined?(ActiveRecord::Normalization::NormalizedValueType) &&
161
162
  ActiveRecord::Normalization::NormalizedValueType === type
162
163
  }
163
- type_for_activerecord_value(column_type.cast_type)
164
+ type_for_activerecord_value(column_type.cast_type, column_nullability:)
164
165
  when ->(type) {
165
166
  defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid) &&
166
167
  ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid === type
@@ -180,12 +181,25 @@ module Tapioca
180
181
  defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array) &&
181
182
  ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array === type
182
183
  }
183
- "T::Array[#{type_for_activerecord_value(column_type.subtype)}]"
184
+ "T::Array[#{type_for_activerecord_value(column_type.subtype, column_nullability:)}]"
184
185
  else
185
- ActiveModelTypeHelper.type_for(column_type)
186
+ as_non_nilable_if_persisted_and_not_nullable(
187
+ ActiveModelTypeHelper.type_for(column_type),
188
+ column_nullability: column_nullability,
189
+ )
186
190
  end
187
191
  end
188
192
 
193
+ sig { params(base_type: String, column_nullability: T::Boolean).returns(String) }
194
+ def as_non_nilable_if_persisted_and_not_nullable(base_type, column_nullability:)
195
+ # It's possible that when ActiveModel::Type::Value is used, the signature being reflected on in
196
+ # ActiveModelTypeHelper.type_for(type_value) may say the type can be nilable. However, if the type is
197
+ # persisted and the column is not nullable, we can assume it's not nilable.
198
+ return as_non_nilable_type(base_type) if @column_type_option.persisted? && !column_nullability
199
+
200
+ base_type
201
+ end
202
+
189
203
  sig { params(column_type: ActiveRecord::Enum::EnumType).returns(String) }
190
204
  def enum_setter_type(column_type)
191
205
  # In Rails < 7 this method is private. When support for that is dropped we can call the method directly
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ class GemInfo < T::Struct
6
+ const :name, String
7
+ const :version, ::Gem::Version
8
+
9
+ class << self
10
+ extend(T::Sig)
11
+
12
+ sig { params(spec: Bundler::LazySpecification).returns(GemInfo) }
13
+ def from_spec(spec)
14
+ new(name: spec.name, version: spec.version)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -19,24 +19,24 @@ require "netrc"
19
19
  require "parallel"
20
20
  require "pathname"
21
21
  require "shellwords"
22
- require "spoom"
23
22
  require "tempfile"
24
23
  require "thor"
25
24
  require "yaml"
26
25
  require "yard-sorbet"
27
26
 
28
27
  require "tapioca/runtime/dynamic_mixin_compiler"
29
- require "tapioca/helpers/gem_helper"
30
-
31
- require "tapioca/helpers/git_attributes"
32
- require "tapioca/helpers/sorbet_helper"
33
- require "tapioca/helpers/rbi_helper"
34
28
  require "tapioca/sorbet_ext/backcompat_patches"
35
29
  require "tapioca/sorbet_ext/name_patch"
36
30
  require "tapioca/sorbet_ext/generic_name_patch"
37
31
  require "tapioca/sorbet_ext/proc_bind_patch"
38
32
  require "tapioca/runtime/generic_type_registry"
39
33
 
34
+ require "spoom"
35
+ require "tapioca/helpers/gem_helper"
36
+ require "tapioca/helpers/git_attributes"
37
+ require "tapioca/helpers/sorbet_helper"
38
+ require "tapioca/helpers/rbi_helper"
39
+
40
40
  require "tapioca/helpers/source_uri"
41
41
  require "tapioca/helpers/cli_helper"
42
42
  require "tapioca/helpers/config_helper"
@@ -45,6 +45,7 @@ require "tapioca/helpers/env_helper"
45
45
 
46
46
  require "tapioca/repo_index"
47
47
  require "tapioca/gemfile"
48
+ require "tapioca/gem_info"
48
49
  require "tapioca/executor"
49
50
 
50
51
  require "tapioca/static/symbol_table_parser"
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.14.4"
5
+ VERSION = "0.15.1"
6
6
  end
data/lib/tapioca.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
5
+ require "rubygems/user_interaction"
5
6
 
6
7
  module Tapioca
7
8
  extend T::Sig
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapioca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.4
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
8
8
  - Alan Wu
9
9
  - Alexandre Terrasa
10
10
  - Peter Zhu
11
- autorequire:
11
+ autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2024-06-20 00:00:00.000000000 Z
14
+ date: 2024-07-10 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -131,7 +131,7 @@ dependencies:
131
131
  - - ">="
132
132
  - !ruby/object:Gem::Version
133
133
  version: '0'
134
- description:
134
+ description:
135
135
  email:
136
136
  - ruby@shopify.com
137
137
  executables:
@@ -228,6 +228,7 @@ files:
228
228
  - lib/tapioca/gem/listeners/subconstants.rb
229
229
  - lib/tapioca/gem/listeners/yard_doc.rb
230
230
  - lib/tapioca/gem/pipeline.rb
231
+ - lib/tapioca/gem_info.rb
231
232
  - lib/tapioca/gemfile.rb
232
233
  - lib/tapioca/helpers/cli_helper.rb
233
234
  - lib/tapioca/helpers/config_helper.rb
@@ -273,7 +274,7 @@ licenses:
273
274
  - MIT
274
275
  metadata:
275
276
  allowed_push_host: https://rubygems.org
276
- post_install_message:
277
+ post_install_message:
277
278
  rdoc_options: []
278
279
  require_paths:
279
280
  - lib
@@ -288,8 +289,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
288
289
  - !ruby/object:Gem::Version
289
290
  version: '0'
290
291
  requirements: []
291
- rubygems_version: 3.5.13
292
- signing_key:
292
+ rubygems_version: 3.5.14
293
+ signing_key:
293
294
  specification_version: 4
294
295
  summary: A Ruby Interface file generator for gems, core types and the Ruby standard
295
296
  library