tapioca 0.16.5 → 0.16.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -1
- data/lib/tapioca/cli.rb +7 -1
- data/lib/tapioca/commands/abstract_gem.rb +4 -34
- data/lib/tapioca/commands/gem_generate.rb +35 -0
- data/lib/tapioca/dsl/compilers/active_model_attributes.rb +11 -11
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +19 -3
- data/lib/tapioca/dsl/compilers/json_api_client_resource.rb +10 -0
- data/lib/tapioca/dsl/helpers/active_model_type_helper.rb +12 -1
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +5 -0
- data/lib/tapioca/gemfile.rb +17 -8
- data/lib/tapioca/loaders/dsl.rb +6 -1
- data/lib/tapioca/version.rb +1 -1
- metadata +3 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a42a478b0396f43dd9e6505c18ac69ca8c32e6ef00b298e6d6e4a8c12536b17
|
4
|
+
data.tar.gz: f24ab38ee1d78f1633f7d580ea5be8f93e13c9c6550d11b5d5bc4171d974929b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f623d3b5e66a081728d9ae4b3dde4c6c976829ffbd6e606a64def2e8aff489c09911396ca72146c901f72a11d432d6c86ae840e278b8d85fd8ee756e97776542
|
7
|
+
data.tar.gz: e7686bcfed28858bfb8f162ee0bd8a7c9a6cdf286c7227a86032025934b04fa72c29f21c41d7a3c5668edb8aad2d39bf2808a2d98799a7434e94aa9b921c3cf8
|
data/README.md
CHANGED
@@ -999,7 +999,16 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/Shopif
|
|
999
999
|
|
1000
1000
|
### DSL compilers
|
1001
1001
|
|
1002
|
-
|
1002
|
+
Tapioca ships with a small collection of high quality DSL compilers for popular Ruby gems that are used heavily at Shopify, like Rails and GraphQL. We encourage the community to contribute new DSL compilers, though they shouldn't necessarily live in the Tapioca repo itself.
|
1003
|
+
|
1004
|
+
It's best for DSL compilers to be contributed directly to gems they apply to ([example](https://github.com/Shopify/measured/tree/main/lib/tapioca/dsl/compilers)). This way, when changes are made to the gem's DSL, the gem's DSL compiler can be updated at the same time and be versioned/released together.
|
1005
|
+
|
1006
|
+
If an upstream gem's maintainers don't wish to host a DSL compiler themselves, you can propose contributing it to:
|
1007
|
+
|
1008
|
+
1. Tapioca, if it's a gem that Shopify uses (ask us in an issue or PR)
|
1009
|
+
2. A third party DSL compiler repository, like [AngelList/Boba](https://github.com/angellist/boba). These are not supported by Shopify.
|
1010
|
+
|
1011
|
+
For help writing a DSL compiler, please refer to [writing custom dsl compilers](https://github.com/Shopify/tapioca?tab=readme-ov-file#writing-custom-dsl-compilers).
|
1003
1012
|
|
1004
1013
|
## License
|
1005
1014
|
|
data/lib/tapioca/cli.rb
CHANGED
@@ -145,7 +145,7 @@ module Tapioca
|
|
145
145
|
default: {}
|
146
146
|
option :lsp_addon,
|
147
147
|
type: :boolean,
|
148
|
-
desc: "Generate DSL RBIs from the LSP
|
148
|
+
desc: "Generate DSL RBIs from the LSP add-on. Internal to Tapioca and not intended for end-users",
|
149
149
|
default: false,
|
150
150
|
hide: true
|
151
151
|
def dsl(*constant_or_paths)
|
@@ -266,6 +266,11 @@ module Tapioca
|
|
266
266
|
type: :boolean,
|
267
267
|
desc: "Halt upon a load error while loading the Rails application",
|
268
268
|
default: true
|
269
|
+
option :lsp_addon,
|
270
|
+
type: :boolean,
|
271
|
+
desc: "Generate Gem RBIs from the LSP add-on. Internal to Tapioca and not intended for end-users",
|
272
|
+
default: false,
|
273
|
+
hide: true
|
269
274
|
def gem(*gems)
|
270
275
|
set_environment(options)
|
271
276
|
|
@@ -300,6 +305,7 @@ module Tapioca
|
|
300
305
|
dsl_dir: options[:dsl_dir],
|
301
306
|
rbi_formatter: rbi_formatter(options),
|
302
307
|
halt_upon_load_error: options[:halt_upon_load_error],
|
308
|
+
lsp_addon: options[:lsp_addon],
|
303
309
|
}
|
304
310
|
|
305
311
|
command = if verify
|
@@ -27,6 +27,7 @@ module Tapioca
|
|
27
27
|
dsl_dir: String,
|
28
28
|
rbi_formatter: RBIFormatter,
|
29
29
|
halt_upon_load_error: T::Boolean,
|
30
|
+
lsp_addon: T.nilable(T::Boolean),
|
30
31
|
).void
|
31
32
|
end
|
32
33
|
def initialize(
|
@@ -45,7 +46,8 @@ module Tapioca
|
|
45
46
|
auto_strictness: true,
|
46
47
|
dsl_dir: DEFAULT_DSL_DIR,
|
47
48
|
rbi_formatter: DEFAULT_RBI_FORMATTER,
|
48
|
-
halt_upon_load_error: true
|
49
|
+
halt_upon_load_error: true,
|
50
|
+
lsp_addon: false
|
49
51
|
)
|
50
52
|
@gem_names = gem_names
|
51
53
|
@exclude = exclude
|
@@ -59,6 +61,7 @@ module Tapioca
|
|
59
61
|
@auto_strictness = auto_strictness
|
60
62
|
@dsl_dir = dsl_dir
|
61
63
|
@rbi_formatter = rbi_formatter
|
64
|
+
@lsp_addon = lsp_addon
|
62
65
|
|
63
66
|
super()
|
64
67
|
|
@@ -73,39 +76,6 @@ module Tapioca
|
|
73
76
|
|
74
77
|
private
|
75
78
|
|
76
|
-
sig { params(gem_names: T::Array[String]).returns(T::Array[Gemfile::GemSpec]) }
|
77
|
-
def gems_to_generate(gem_names)
|
78
|
-
return @bundle.dependencies if gem_names.empty?
|
79
|
-
|
80
|
-
gem_names.each_with_object([]) do |gem_name, gems|
|
81
|
-
gem = @bundle.gem(gem_name)
|
82
|
-
|
83
|
-
if gem.nil?
|
84
|
-
raise Thor::Error, set_color("Error: Cannot find gem '#{gem_name}'", :red)
|
85
|
-
end
|
86
|
-
|
87
|
-
gems.concat(gem_dependencies(gem)) if @include_dependencies
|
88
|
-
gems << gem
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
sig do
|
93
|
-
params(
|
94
|
-
gem: Gemfile::GemSpec,
|
95
|
-
dependencies: T::Array[Gemfile::GemSpec],
|
96
|
-
).returns(T::Array[Gemfile::GemSpec])
|
97
|
-
end
|
98
|
-
def gem_dependencies(gem, dependencies = [])
|
99
|
-
direct_dependencies = gem.dependencies.filter_map { |dependency| @bundle.gem(dependency.name) }
|
100
|
-
gems = dependencies | direct_dependencies
|
101
|
-
|
102
|
-
if direct_dependencies.empty?
|
103
|
-
gems
|
104
|
-
else
|
105
|
-
direct_dependencies.reduce(gems) { |result, gem| gem_dependencies(gem, result) }
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
79
|
sig { params(gem: Gemfile::GemSpec).void }
|
110
80
|
def compile_gem_rbi(gem)
|
111
81
|
gem_name = set_color(gem.name, :yellow, :bold)
|
@@ -46,6 +46,41 @@ module Tapioca
|
|
46
46
|
ensure
|
47
47
|
GitAttributes.create_generated_attribute_file(@outpath)
|
48
48
|
end
|
49
|
+
|
50
|
+
sig { params(gem_names: T::Array[String]).returns(T::Array[Gemfile::GemSpec]) }
|
51
|
+
def gems_to_generate(gem_names)
|
52
|
+
return @bundle.dependencies if gem_names.empty?
|
53
|
+
|
54
|
+
(gem_names - @exclude).each_with_object([]) do |gem_name, gems|
|
55
|
+
gem = @bundle.gem(gem_name)
|
56
|
+
|
57
|
+
if gem.nil?
|
58
|
+
next if @lsp_addon
|
59
|
+
|
60
|
+
raise Thor::Error, set_color("Error: Cannot find gem '#{gem_name}'", :red)
|
61
|
+
end
|
62
|
+
|
63
|
+
gems.concat(gem_dependencies(gem)) if @include_dependencies
|
64
|
+
gems << gem
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
sig do
|
69
|
+
params(
|
70
|
+
gem: Gemfile::GemSpec,
|
71
|
+
dependencies: T::Array[Gemfile::GemSpec],
|
72
|
+
).returns(T::Array[Gemfile::GemSpec])
|
73
|
+
end
|
74
|
+
def gem_dependencies(gem, dependencies = [])
|
75
|
+
direct_dependencies = gem.dependencies.filter_map { |dependency| @bundle.gem(dependency.name) }
|
76
|
+
gems = dependencies | direct_dependencies
|
77
|
+
|
78
|
+
if direct_dependencies.empty?
|
79
|
+
gems
|
80
|
+
else
|
81
|
+
direct_dependencies.reduce(gems) { |result, gem| gem_dependencies(gem, result) }
|
82
|
+
end
|
83
|
+
end
|
49
84
|
end
|
50
85
|
end
|
51
86
|
end
|
@@ -101,26 +101,26 @@ module Tapioca
|
|
101
101
|
|
102
102
|
sig { params(attribute_type_value: T.untyped).returns(::String) }
|
103
103
|
def type_for(attribute_type_value)
|
104
|
-
|
104
|
+
case attribute_type_value
|
105
105
|
when ActiveModel::Type::Boolean
|
106
|
-
"T::Boolean"
|
106
|
+
as_nilable_type("T::Boolean")
|
107
107
|
when ActiveModel::Type::Date
|
108
|
-
"::Date"
|
108
|
+
as_nilable_type("::Date")
|
109
109
|
when ActiveModel::Type::DateTime, ActiveModel::Type::Time
|
110
|
-
"::Time"
|
110
|
+
as_nilable_type("::Time")
|
111
111
|
when ActiveModel::Type::Decimal
|
112
|
-
"::BigDecimal"
|
112
|
+
as_nilable_type("::BigDecimal")
|
113
113
|
when ActiveModel::Type::Float
|
114
|
-
"::Float"
|
114
|
+
as_nilable_type("::Float")
|
115
115
|
when ActiveModel::Type::Integer
|
116
|
-
"::Integer"
|
116
|
+
as_nilable_type("::Integer")
|
117
117
|
when ActiveModel::Type::String
|
118
|
-
"::String"
|
118
|
+
as_nilable_type("::String")
|
119
119
|
else
|
120
|
-
Helpers::ActiveModelTypeHelper.type_for(attribute_type_value)
|
120
|
+
type = Helpers::ActiveModelTypeHelper.type_for(attribute_type_value)
|
121
|
+
type = as_nilable_type(type) if Helpers::ActiveModelTypeHelper.assume_nilable?(attribute_type_value)
|
122
|
+
type
|
121
123
|
end
|
122
|
-
|
123
|
-
as_nilable_type(type)
|
124
124
|
end
|
125
125
|
|
126
126
|
sig { params(klass: RBI::Scope, method: String, type: String).void }
|
@@ -372,7 +372,7 @@ module Tapioca
|
|
372
372
|
return_type: "T.self_type",
|
373
373
|
)
|
374
374
|
|
375
|
-
CALCULATION_METHODS.each do |method_name|
|
375
|
+
(CALCULATION_METHODS + [:size]).each do |method_name|
|
376
376
|
case method_name
|
377
377
|
when :average, :maximum, :minimum
|
378
378
|
klass.create_method(
|
@@ -400,9 +400,9 @@ module Tapioca
|
|
400
400
|
],
|
401
401
|
return_type: "T::Hash[T.untyped, Integer]",
|
402
402
|
)
|
403
|
-
when :sum
|
403
|
+
when :sum, :size
|
404
404
|
klass.create_method(
|
405
|
-
|
405
|
+
method_name.to_s,
|
406
406
|
parameters: [
|
407
407
|
create_opt_param("column_name", type: "T.nilable(T.any(String, Symbol))", default: "nil"),
|
408
408
|
create_block_param("block", type: "T.nilable(T.proc.params(record: T.untyped).returns(T.untyped))"),
|
@@ -586,6 +586,22 @@ module Tapioca
|
|
586
586
|
parameters: parameters,
|
587
587
|
return_type: return_type,
|
588
588
|
)
|
589
|
+
when :select
|
590
|
+
[relation_methods_module, association_relation_methods_module].each do |mod|
|
591
|
+
mod.create_method(method_name.to_s) do |method|
|
592
|
+
method.add_rest_param("args")
|
593
|
+
method.add_block_param("blk")
|
594
|
+
|
595
|
+
method.add_sig do |sig|
|
596
|
+
sig.add_param("args", "T.untyped")
|
597
|
+
sig.return_type = mod == relation_methods_module ? RelationClassName : AssociationRelationClassName
|
598
|
+
end
|
599
|
+
method.add_sig do |sig|
|
600
|
+
sig.add_param("blk", "T.proc.params(record: #{constant_name}).returns(T::Boolean)")
|
601
|
+
sig.return_type = "T::Array[#{constant_name}]"
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
589
605
|
else
|
590
606
|
create_relation_method(
|
591
607
|
method_name,
|
@@ -147,7 +147,17 @@ module Tapioca
|
|
147
147
|
return "T.untyped" if type.nil?
|
148
148
|
|
149
149
|
sorbet_type = if type.respond_to?(:sorbet_type)
|
150
|
+
line, file = type.method(:sorbet_type).source_location
|
151
|
+
|
152
|
+
$stderr.puts <<~MESSAGE
|
153
|
+
WARNING: `#sorbet_type` is deprecated. Please rename your method to `#__tapioca_type`."
|
154
|
+
|
155
|
+
Defined on line #{line} of #{file}
|
156
|
+
MESSAGE
|
157
|
+
|
150
158
|
type.sorbet_type
|
159
|
+
elsif type.respond_to?(:__tapioca_type)
|
160
|
+
type.__tapioca_type
|
151
161
|
elsif type == ::JsonApiClient::Schema::Types::Integer
|
152
162
|
"::Integer"
|
153
163
|
elsif type == ::JsonApiClient::Schema::Types::String
|
@@ -14,7 +14,8 @@ module Tapioca
|
|
14
14
|
def type_for(type_value)
|
15
15
|
return "T.untyped" if Runtime::GenericTypeRegistry.generic_type_instance?(type_value)
|
16
16
|
|
17
|
-
type =
|
17
|
+
type = lookup_tapioca_type(type_value) ||
|
18
|
+
lookup_return_type_of_method(type_value, :deserialize) ||
|
18
19
|
lookup_return_type_of_method(type_value, :cast) ||
|
19
20
|
lookup_return_type_of_method(type_value, :cast_value) ||
|
20
21
|
lookup_arg_type_of_method(type_value, :serialize) ||
|
@@ -22,6 +23,11 @@ module Tapioca
|
|
22
23
|
type.to_s
|
23
24
|
end
|
24
25
|
|
26
|
+
sig { params(type_value: T.untyped).returns(T::Boolean) }
|
27
|
+
def assume_nilable?(type_value)
|
28
|
+
!type_value.respond_to?(:__tapioca_type)
|
29
|
+
end
|
30
|
+
|
25
31
|
private
|
26
32
|
|
27
33
|
MEANINGLESS_TYPES = T.let(
|
@@ -39,6 +45,11 @@ module Tapioca
|
|
39
45
|
!MEANINGLESS_TYPES.include?(type)
|
40
46
|
end
|
41
47
|
|
48
|
+
sig { params(obj: T.untyped).returns(T.nilable(T::Types::Base)) }
|
49
|
+
def lookup_tapioca_type(obj)
|
50
|
+
T::Utils.coerce(obj.__tapioca_type) if obj.respond_to?(:__tapioca_type)
|
51
|
+
end
|
52
|
+
|
42
53
|
sig { params(obj: T.untyped, method: Symbol).returns(T.nilable(T::Types::Base)) }
|
43
54
|
def lookup_return_type_of_method(obj, method)
|
44
55
|
return_type = lookup_signature_of_method(obj, method)&.return_type
|
@@ -211,6 +211,11 @@ module Tapioca
|
|
211
211
|
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Range === type
|
212
212
|
}
|
213
213
|
"T::Range[#{type_for_activerecord_value(column_type.subtype, column_nullability:)}]"
|
214
|
+
when ->(type) {
|
215
|
+
defined?(ActiveRecord::Locking::LockingType) &&
|
216
|
+
ActiveRecord::Locking::LockingType === type
|
217
|
+
}
|
218
|
+
as_non_nilable_if_persisted_and_not_nullable("::Integer", column_nullability:)
|
214
219
|
else
|
215
220
|
as_non_nilable_if_persisted_and_not_nullable(
|
216
221
|
ActiveModelTypeHelper.type_for(column_type),
|
data/lib/tapioca/gemfile.rb
CHANGED
@@ -66,15 +66,24 @@ module Tapioca
|
|
66
66
|
|
67
67
|
sig { returns([T::Enumerable[Spec], T::Array[String]]) }
|
68
68
|
def materialize_deps
|
69
|
-
deps = definition.locked_gems.dependencies.values
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
69
|
+
deps = definition.locked_gems.dependencies.except(*@excluded_gems).values
|
70
|
+
resolve = definition.resolve
|
71
|
+
materialized_dependencies = resolve.materialize(deps)
|
72
|
+
|
73
|
+
if Bundler::VERSION >= "2.6.0"
|
74
|
+
missing_specs = resolve.missing_specs.map do |spec|
|
75
|
+
"#{spec.name} (#{spec.version})"
|
76
|
+
end
|
77
|
+
else
|
78
|
+
missing_spec_names = materialized_dependencies.missing_specs.map(&:name).to_set
|
79
|
+
missing_specs = materialized_dependencies.missing_specs.map do |spec|
|
80
|
+
"#{spec.name} (#{spec.version})"
|
81
|
+
end
|
82
|
+
materialized_dependencies = materialized_dependencies.to_a.reject do |spec|
|
83
|
+
missing_spec_names.include?(spec.name)
|
84
|
+
end
|
77
85
|
end
|
86
|
+
|
78
87
|
[materialized_dependencies, missing_specs]
|
79
88
|
end
|
80
89
|
|
data/lib/tapioca/loaders/dsl.rb
CHANGED
@@ -85,7 +85,12 @@ module Tapioca
|
|
85
85
|
def load_dsl_compilers
|
86
86
|
say("Loading DSL compiler classes... ")
|
87
87
|
|
88
|
-
# Load built-in compilers
|
88
|
+
# Load all built-in compilers before any custom compilers
|
89
|
+
Dir.glob("#{Tapioca::LIB_ROOT_DIR}/tapioca/dsl/compilers/*.rb").each do |compiler|
|
90
|
+
require File.expand_path(compiler)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Load all custom compilers exported from gems
|
89
94
|
::Gem.find_files("tapioca/dsl/compilers/*.rb").each do |compiler|
|
90
95
|
require File.expand_path(compiler)
|
91
96
|
end
|
data/lib/tapioca/version.rb
CHANGED
metadata
CHANGED
@@ -1,17 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapioca
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.16.
|
4
|
+
version: 0.16.6
|
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:
|
12
11
|
bindir: exe
|
13
12
|
cert_chain: []
|
14
|
-
date:
|
13
|
+
date: 2025-01-06 00:00:00.000000000 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
16
|
name: bundler
|
@@ -125,7 +124,6 @@ dependencies:
|
|
125
124
|
- - ">="
|
126
125
|
- !ruby/object:Gem::Version
|
127
126
|
version: '0'
|
128
|
-
description:
|
129
127
|
email:
|
130
128
|
- ruby@shopify.com
|
131
129
|
executables:
|
@@ -271,7 +269,6 @@ licenses:
|
|
271
269
|
- MIT
|
272
270
|
metadata:
|
273
271
|
allowed_push_host: https://rubygems.org
|
274
|
-
post_install_message:
|
275
272
|
rdoc_options: []
|
276
273
|
require_paths:
|
277
274
|
- lib
|
@@ -286,8 +283,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
286
283
|
- !ruby/object:Gem::Version
|
287
284
|
version: '0'
|
288
285
|
requirements: []
|
289
|
-
rubygems_version: 3.
|
290
|
-
signing_key:
|
286
|
+
rubygems_version: 3.6.2
|
291
287
|
specification_version: 4
|
292
288
|
summary: A Ruby Interface file generator for gems, core types and the Ruby standard
|
293
289
|
library
|