tapioca 0.14.4 → 0.15.1

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