tapioca 0.11.8 → 0.11.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +116 -49
- data/lib/tapioca/cli.rb +76 -67
- data/lib/tapioca/commands/{dsl.rb → abstract_dsl.rb} +32 -78
- data/lib/tapioca/commands/{gem.rb → abstract_gem.rb} +26 -93
- data/lib/tapioca/commands/annotations.rb +9 -7
- data/lib/tapioca/commands/check_shims.rb +2 -0
- data/lib/tapioca/commands/command.rb +9 -2
- data/lib/tapioca/commands/configure.rb +2 -2
- data/lib/tapioca/commands/dsl_compiler_list.rb +31 -0
- data/lib/tapioca/commands/dsl_generate.rb +40 -0
- data/lib/tapioca/commands/dsl_verify.rb +25 -0
- data/lib/tapioca/commands/gem_generate.rb +51 -0
- data/lib/tapioca/commands/gem_sync.rb +37 -0
- data/lib/tapioca/commands/gem_verify.rb +36 -0
- data/lib/tapioca/commands/require.rb +2 -0
- data/lib/tapioca/commands/todo.rb +21 -2
- data/lib/tapioca/commands.rb +8 -2
- data/lib/tapioca/dsl/compiler.rb +8 -4
- data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +3 -1
- data/lib/tapioca/dsl/compilers/active_model_validations_confirmation.rb +94 -0
- data/lib/tapioca/dsl/compilers/active_record_columns.rb +19 -9
- data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +11 -14
- data/lib/tapioca/dsl/compilers/active_record_store.rb +149 -0
- data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +3 -2
- data/lib/tapioca/dsl/compilers/active_support_concern.rb +1 -1
- data/lib/tapioca/dsl/compilers/graphql_input_object.rb +1 -1
- data/lib/tapioca/dsl/compilers/graphql_mutation.rb +9 -2
- data/lib/tapioca/dsl/compilers/json_api_client_resource.rb +208 -0
- data/lib/tapioca/dsl/extensions/active_record.rb +9 -0
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +23 -4
- data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +1 -0
- data/lib/tapioca/dsl/helpers/graphql_type_helper.rb +18 -3
- data/lib/tapioca/dsl/pipeline.rb +6 -4
- data/lib/tapioca/gem/pipeline.rb +103 -36
- data/lib/tapioca/gemfile.rb +13 -7
- data/lib/tapioca/helpers/git_attributes.rb +34 -0
- data/lib/tapioca/helpers/test/template.rb +4 -4
- data/lib/tapioca/internal.rb +1 -0
- data/lib/tapioca/loaders/dsl.rb +11 -1
- data/lib/tapioca/loaders/loader.rb +17 -2
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +0 -27
- data/lib/tapioca/static/symbol_loader.rb +10 -9
- data/lib/tapioca/version.rb +1 -1
- metadata +20 -10
@@ -0,0 +1,208 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "json_api_client"
|
6
|
+
rescue LoadError
|
7
|
+
# means JsonApiClient is not installed,
|
8
|
+
# so let's not even define the compiler.
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
module Tapioca
|
13
|
+
module Dsl
|
14
|
+
module Compilers
|
15
|
+
# `Tapioca::Dsl::Compilers::JsonApiClientResource` generates RBI files for classes that inherit
|
16
|
+
# [`JsonApiClient::Resource`](https://github.com/JsonApiClient/json_api_client).
|
17
|
+
#
|
18
|
+
# For example, with the following classes that inherits `JsonApiClient::Resource`:
|
19
|
+
#
|
20
|
+
# ~~~rb
|
21
|
+
# # user.rb
|
22
|
+
# class User < JsonApiClient::Resource
|
23
|
+
# has_many :posts
|
24
|
+
#
|
25
|
+
# property :name, type: :string
|
26
|
+
# property :is_admin, type: :boolean, default: false
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # post.rb
|
30
|
+
# class Post < JsonApiClient::Resource
|
31
|
+
# belongs_to :user
|
32
|
+
#
|
33
|
+
# property :title, type: :string
|
34
|
+
# end
|
35
|
+
# ~~~
|
36
|
+
#
|
37
|
+
# this compiler will produce RBI files with the following content:
|
38
|
+
#
|
39
|
+
# ~~~rbi
|
40
|
+
# # user.rbi
|
41
|
+
# # typed: strong
|
42
|
+
#
|
43
|
+
# class User
|
44
|
+
# include JsonApiClientResourceGeneratedMethods
|
45
|
+
#
|
46
|
+
# module JsonApiClientResourceGeneratedMethods
|
47
|
+
# sig { returns(T::Boolean) }
|
48
|
+
# def is_admin; end
|
49
|
+
#
|
50
|
+
# sig { params(is_admin: T::Boolean).returns(T::Boolean) }
|
51
|
+
# def is_admin=(is_admin); end
|
52
|
+
#
|
53
|
+
# sig { returns(T.nilable(::String)) }
|
54
|
+
# def name; end
|
55
|
+
#
|
56
|
+
# sig { params(name: T.nilable(::String)).returns(T.nilable(::String)) }
|
57
|
+
# def name=(name); end
|
58
|
+
#
|
59
|
+
# sig { returns(T.nilable(T::Array[Post])) }
|
60
|
+
# def posts; end
|
61
|
+
#
|
62
|
+
# sig { params(posts: T.nilable(T::Array[Post])).returns(T.nilable(T::Array[Post])) }
|
63
|
+
# def posts=(posts); end
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# # post.rbi
|
68
|
+
# # typed: strong
|
69
|
+
#
|
70
|
+
# class Post
|
71
|
+
# include JsonApiClientResourceGeneratedMethods
|
72
|
+
#
|
73
|
+
# module JsonApiClientResourceGeneratedMethods
|
74
|
+
# sig { returns(T.nilable(::String)) }
|
75
|
+
# def title; end
|
76
|
+
#
|
77
|
+
# sig { params(title: T.nilable(::String)).returns(T.nilable(::String)) }
|
78
|
+
# def title=(title); end
|
79
|
+
#
|
80
|
+
# sig { returns(T.nilable(::String)) }
|
81
|
+
# def user_id; end
|
82
|
+
#
|
83
|
+
# sig { params(user_id: T.nilable(::String)).returns(T.nilable(::String)) }
|
84
|
+
# def user_id=(user_id); end
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
# ~~~
|
88
|
+
class JsonApiClientResource < Compiler
|
89
|
+
extend T::Sig
|
90
|
+
|
91
|
+
ConstantType = type_member { { fixed: T.class_of(::JsonApiClient::Resource) } }
|
92
|
+
|
93
|
+
sig { override.void }
|
94
|
+
def decorate
|
95
|
+
schema = resource_schema
|
96
|
+
return if schema.nil? && constant.associations.empty?
|
97
|
+
|
98
|
+
root.create_path(constant) do |k|
|
99
|
+
module_name = "JsonApiClientResourceGeneratedMethods"
|
100
|
+
k.create_module(module_name) do |mod|
|
101
|
+
schema&.each_property do |property|
|
102
|
+
generate_methods_for_property(mod, property)
|
103
|
+
end
|
104
|
+
|
105
|
+
constant.associations.each do |association|
|
106
|
+
generate_methods_for_association(mod, association)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
k.create_include(module_name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class << self
|
115
|
+
extend T::Sig
|
116
|
+
|
117
|
+
sig { override.returns(T::Enumerable[Module]) }
|
118
|
+
def gather_constants
|
119
|
+
all_modules.select do |c|
|
120
|
+
name_of(c) && c < ::JsonApiClient::Resource
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
sig { returns(T.nilable(::JsonApiClient::Schema)) }
|
128
|
+
def resource_schema
|
129
|
+
schema = constant.schema
|
130
|
+
|
131
|
+
# empty? does not exist on JsonApiClient::Schema
|
132
|
+
schema if schema.size > 0 # rubocop:disable Style/ZeroLengthPredicate
|
133
|
+
end
|
134
|
+
|
135
|
+
sig do
|
136
|
+
params(
|
137
|
+
mod: RBI::Scope,
|
138
|
+
property: ::JsonApiClient::Schema::Property,
|
139
|
+
).void
|
140
|
+
end
|
141
|
+
def generate_methods_for_property(mod, property)
|
142
|
+
type = type_for(property)
|
143
|
+
|
144
|
+
name = property.name.to_s
|
145
|
+
|
146
|
+
mod.create_method(name, return_type: type)
|
147
|
+
mod.create_method("#{name}=", parameters: [create_param(name, type: type)], return_type: type)
|
148
|
+
end
|
149
|
+
|
150
|
+
sig { params(property: ::JsonApiClient::Schema::Property).returns(String) }
|
151
|
+
def type_for(property)
|
152
|
+
type = ::JsonApiClient::Schema::TypeFactory.type_for(property.type)
|
153
|
+
return "T.untyped" if type.nil?
|
154
|
+
|
155
|
+
sorbet_type = if type.respond_to?(:sorbet_type)
|
156
|
+
type.sorbet_type
|
157
|
+
elsif type == ::JsonApiClient::Schema::Types::Integer
|
158
|
+
"::Integer"
|
159
|
+
elsif type == ::JsonApiClient::Schema::Types::String
|
160
|
+
"::String"
|
161
|
+
elsif type == ::JsonApiClient::Schema::Types::Float
|
162
|
+
"::Float"
|
163
|
+
elsif type == ::JsonApiClient::Schema::Types::Time
|
164
|
+
"::Time"
|
165
|
+
elsif type == ::JsonApiClient::Schema::Types::Decimal
|
166
|
+
"::BigDecimal"
|
167
|
+
elsif type == ::JsonApiClient::Schema::Types::Boolean
|
168
|
+
"T::Boolean"
|
169
|
+
else
|
170
|
+
"T.untyped"
|
171
|
+
end
|
172
|
+
|
173
|
+
if property.default.nil?
|
174
|
+
as_nilable_type(sorbet_type)
|
175
|
+
else
|
176
|
+
sorbet_type
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
sig do
|
181
|
+
params(
|
182
|
+
mod: RBI::Scope,
|
183
|
+
association: JsonApiClient::Associations::BaseAssociation,
|
184
|
+
).void
|
185
|
+
end
|
186
|
+
def generate_methods_for_association(mod, association)
|
187
|
+
# If the association is broken, it will raise a NameError when trying to access the association_class
|
188
|
+
klass = association.association_class
|
189
|
+
|
190
|
+
name, type = case association
|
191
|
+
when ::JsonApiClient::Associations::BelongsTo::Association
|
192
|
+
# id must be a string: # https://jsonapi.org/format/#document-resource-object-identification
|
193
|
+
[association.param.to_s, "T.nilable(::String)"]
|
194
|
+
when ::JsonApiClient::Associations::HasOne::Association
|
195
|
+
[association.attr_name.to_s, "T.nilable(#{klass})"]
|
196
|
+
when ::JsonApiClient::Associations::HasMany::Association
|
197
|
+
[association.attr_name.to_s, "T.nilable(T::Array[#{klass}])"]
|
198
|
+
else
|
199
|
+
return # Unsupported association type
|
200
|
+
end
|
201
|
+
|
202
|
+
mod.create_method(name, return_type: type)
|
203
|
+
mod.create_method("#{name}=", parameters: [create_param(name, type: type)], return_type: type)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -30,6 +30,15 @@ module Tapioca
|
|
30
30
|
super
|
31
31
|
end
|
32
32
|
|
33
|
+
attr_reader :__tapioca_stored_attributes
|
34
|
+
|
35
|
+
def store_accessor(store_attribute, *keys, prefix: nil, suffix: nil)
|
36
|
+
@__tapioca_stored_attributes ||= []
|
37
|
+
@__tapioca_stored_attributes << [store_attribute, keys, prefix, suffix]
|
38
|
+
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
33
42
|
::ActiveRecord::Base.singleton_class.prepend(self)
|
34
43
|
end
|
35
44
|
end
|
@@ -13,8 +13,26 @@ module Tapioca
|
|
13
13
|
@constant = constant
|
14
14
|
end
|
15
15
|
|
16
|
+
sig { params(attribute_name: String, column_name: String).returns([String, String]) }
|
17
|
+
def type_for(attribute_name, column_name = attribute_name)
|
18
|
+
return id_type if attribute_name == "id"
|
19
|
+
|
20
|
+
column_type_for(column_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
sig { returns([String, String]) }
|
26
|
+
def id_type
|
27
|
+
if @constant.respond_to?(:composite_primary_key?) && T.unsafe(@constant).composite_primary_key?
|
28
|
+
@constant.primary_key.map(&method(:column_type_for)).map { |tuple| "[#{tuple.join(", ")}]" }
|
29
|
+
else
|
30
|
+
column_type_for(@constant.primary_key)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
16
34
|
sig { params(column_name: String).returns([String, String]) }
|
17
|
-
def
|
35
|
+
def column_type_for(column_name)
|
18
36
|
return ["T.untyped", "T.untyped"] if do_not_generate_strong_types?(@constant)
|
19
37
|
|
20
38
|
column = @constant.columns_hash[column_name]
|
@@ -33,7 +51,7 @@ module Tapioca
|
|
33
51
|
return [getter_type, as_nilable_type(setter_type)]
|
34
52
|
end
|
35
53
|
|
36
|
-
if
|
54
|
+
if Array(@constant.primary_key).include?(column_name) ||
|
37
55
|
column_name == "created_at" ||
|
38
56
|
column_name == "updated_at"
|
39
57
|
getter_type = as_nilable_type(getter_type)
|
@@ -42,8 +60,6 @@ module Tapioca
|
|
42
60
|
[getter_type, setter_type]
|
43
61
|
end
|
44
62
|
|
45
|
-
private
|
46
|
-
|
47
63
|
sig { params(column_type: T.untyped).returns(String) }
|
48
64
|
def type_for_activerecord_value(column_type)
|
49
65
|
case column_type
|
@@ -69,6 +85,9 @@ module Tapioca
|
|
69
85
|
"::String"
|
70
86
|
when ActiveRecord::Type::Serialized
|
71
87
|
serialized_column_type(column_type)
|
88
|
+
when defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid) &&
|
89
|
+
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Uuid
|
90
|
+
"::String"
|
72
91
|
when defined?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Hstore) &&
|
73
92
|
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Hstore
|
74
93
|
"T::Hash[::String, ::String]"
|
@@ -15,6 +15,7 @@ module Tapioca
|
|
15
15
|
AssociationMethodsModuleName = T.let("GeneratedAssociationMethods", String)
|
16
16
|
DelegatedTypesModuleName = T.let("GeneratedDelegatedTypeMethods", String)
|
17
17
|
SecureTokensModuleName = T.let("GeneratedSecureTokenMethods", String)
|
18
|
+
StoredAttributesModuleName = T.let("GeneratedStoredAttributesMethods", String)
|
18
19
|
|
19
20
|
RelationMethodsModuleName = T.let("GeneratedRelationMethods", String)
|
20
21
|
AssociationRelationMethodsModuleName = T.let("GeneratedAssociationRelationMethods", String)
|
@@ -9,8 +9,16 @@ module Tapioca
|
|
9
9
|
|
10
10
|
extend T::Sig
|
11
11
|
|
12
|
-
sig { params(
|
13
|
-
def type_for(
|
12
|
+
sig { params(argument: GraphQL::Schema::Argument).returns(String) }
|
13
|
+
def type_for(argument)
|
14
|
+
type = if argument.loads
|
15
|
+
loads_type = ::GraphQL::Schema::Wrapper.new(argument.loads)
|
16
|
+
loads_type = loads_type.to_list_type if argument.type.list?
|
17
|
+
loads_type = loads_type.to_non_null_type if argument.type.non_null?
|
18
|
+
loads_type
|
19
|
+
else
|
20
|
+
argument.type
|
21
|
+
end
|
14
22
|
unwrapped_type = type.unwrap
|
15
23
|
|
16
24
|
parsed_type = case unwrapped_type
|
@@ -39,6 +47,8 @@ module Tapioca
|
|
39
47
|
end
|
40
48
|
when GraphQL::Schema::InputObject.singleton_class
|
41
49
|
type_for_constant(unwrapped_type)
|
50
|
+
when Module
|
51
|
+
Runtime::Reflection.qualified_name_of(unwrapped_type) || "T.untyped"
|
42
52
|
else
|
43
53
|
"T.untyped"
|
44
54
|
end
|
@@ -47,7 +57,7 @@ module Tapioca
|
|
47
57
|
parsed_type = "T::Array[#{parsed_type}]"
|
48
58
|
end
|
49
59
|
|
50
|
-
unless type.non_null?
|
60
|
+
unless type.non_null? || has_replaceable_default?(argument)
|
51
61
|
parsed_type = RBIHelper.as_nilable_type(parsed_type)
|
52
62
|
end
|
53
63
|
|
@@ -60,6 +70,11 @@ module Tapioca
|
|
60
70
|
def type_for_constant(constant)
|
61
71
|
Runtime::Reflection.qualified_name_of(constant) || "T.untyped"
|
62
72
|
end
|
73
|
+
|
74
|
+
sig { params(argument: GraphQL::Schema::Argument).returns(T::Boolean) }
|
75
|
+
def has_replaceable_default?(argument)
|
76
|
+
!!argument.replace_null_with_default? && !argument.default_value.nil?
|
77
|
+
end
|
63
78
|
end
|
64
79
|
end
|
65
80
|
end
|
data/lib/tapioca/dsl/pipeline.rb
CHANGED
@@ -105,10 +105,12 @@ module Tapioca
|
|
105
105
|
|
106
106
|
sig { returns(T::Array[T.class_of(Compiler)]) }
|
107
107
|
def compilers
|
108
|
-
@compilers
|
109
|
-
|
110
|
-
|
111
|
-
|
108
|
+
@compilers ||= T.let(
|
109
|
+
Runtime::Reflection.descendants_of(Compiler).sort_by do |compiler|
|
110
|
+
T.must(compiler.name)
|
111
|
+
end,
|
112
|
+
T.nilable(T::Array[T.class_of(Compiler)]),
|
113
|
+
)
|
112
114
|
end
|
113
115
|
|
114
116
|
private
|
data/lib/tapioca/gem/pipeline.rb
CHANGED
@@ -50,6 +50,8 @@ module Tapioca
|
|
50
50
|
@root
|
51
51
|
end
|
52
52
|
|
53
|
+
# Events handling
|
54
|
+
|
53
55
|
sig { params(symbol: String).void }
|
54
56
|
def push_symbol(symbol)
|
55
57
|
@events << Gem::SymbolFound.new(symbol)
|
@@ -98,6 +100,8 @@ module Tapioca
|
|
98
100
|
@events << Gem::MethodNodeAdded.new(symbol, constant, method, node, signature, parameters)
|
99
101
|
end
|
100
102
|
|
103
|
+
# Constants and properties filtering
|
104
|
+
|
101
105
|
sig { params(symbol_name: String).returns(T::Boolean) }
|
102
106
|
def symbol_in_payload?(symbol_name)
|
103
107
|
symbol_name = symbol_name[2..-1] if symbol_name.start_with?("::")
|
@@ -106,16 +110,27 @@ module Tapioca
|
|
106
110
|
@payload_symbols.include?(symbol_name)
|
107
111
|
end
|
108
112
|
|
113
|
+
# this looks something like:
|
114
|
+
# "(eval at /path/to/file.rb:123)"
|
115
|
+
# and we are just interested in the "/path/to/file.rb" part
|
116
|
+
EVAL_SOURCE_FILE_PATTERN = T.let(/\(eval at (.+):\d+\)/, Regexp)
|
117
|
+
|
109
118
|
sig { params(name: T.any(String, Symbol)).returns(T::Boolean) }
|
110
119
|
def constant_in_gem?(name)
|
111
120
|
return true unless Object.respond_to?(:const_source_location)
|
112
121
|
|
113
|
-
|
114
|
-
return true unless
|
122
|
+
source_file, _ = Object.const_source_location(name)
|
123
|
+
return true unless source_file
|
115
124
|
# If the source location of the constant is "(eval)", all bets are off.
|
116
|
-
return true if
|
125
|
+
return true if source_file == "(eval)"
|
117
126
|
|
118
|
-
|
127
|
+
# Ruby 3.3 adds automatic definition of source location for evals if
|
128
|
+
# `file` and `line` arguments are not provided. This results in the source
|
129
|
+
# file being something like `(eval at /path/to/file.rb:123)`. We try to parse
|
130
|
+
# this string to get the actual source file.
|
131
|
+
source_file = source_file.sub(EVAL_SOURCE_FILE_PATTERN, "\\1")
|
132
|
+
|
133
|
+
gem.contains_path?(source_file)
|
119
134
|
end
|
120
135
|
|
121
136
|
sig { params(method: UnboundMethod).returns(T::Boolean) }
|
@@ -126,6 +141,8 @@ module Tapioca
|
|
126
141
|
@gem.contains_path?(source_location)
|
127
142
|
end
|
128
143
|
|
144
|
+
# Helpers
|
145
|
+
|
129
146
|
sig { params(constant: Module).returns(T.nilable(String)) }
|
130
147
|
def name_of(constant)
|
131
148
|
name = name_of_proxy_target(constant, super(class_of(constant)))
|
@@ -149,6 +166,8 @@ module Tapioca
|
|
149
166
|
gem_symbols.union(engine_symbols)
|
150
167
|
end
|
151
168
|
|
169
|
+
# Events handling
|
170
|
+
|
152
171
|
sig { returns(Gem::Event) }
|
153
172
|
def next_event
|
154
173
|
T.must(@events.shift)
|
@@ -171,7 +190,7 @@ module Tapioca
|
|
171
190
|
sig { params(event: Gem::SymbolFound).void }
|
172
191
|
def on_symbol(event)
|
173
192
|
symbol = event.symbol.delete_prefix("::")
|
174
|
-
return if
|
193
|
+
return if skip_symbol?(symbol)
|
175
194
|
|
176
195
|
constant = constantize(symbol)
|
177
196
|
push_constant(symbol, constant) if Runtime::Reflection.constant_defined?(constant)
|
@@ -180,13 +199,7 @@ module Tapioca
|
|
180
199
|
sig { params(event: Gem::ConstantFound).void.checked(:never) }
|
181
200
|
def on_constant(event)
|
182
201
|
name = event.symbol
|
183
|
-
|
184
|
-
return if name.strip.empty?
|
185
|
-
return if name.start_with?("#<")
|
186
|
-
return if name.downcase == name
|
187
|
-
return if alias_namespaced?(name)
|
188
|
-
|
189
|
-
return if T::Enum === event.constant # T::Enum instances are defined via `compile_enums`
|
202
|
+
return if skip_constant?(name, event.constant)
|
190
203
|
|
191
204
|
if event.is_a?(Gem::ForeignConstantFound)
|
192
205
|
compile_foreign_constant(name, event.constant)
|
@@ -200,11 +213,17 @@ module Tapioca
|
|
200
213
|
@node_listeners.each { |listener| listener.dispatch(event) }
|
201
214
|
end
|
202
215
|
|
203
|
-
#
|
216
|
+
# Compiling
|
204
217
|
|
205
218
|
sig { params(symbol: String, constant: Module).void }
|
206
219
|
def compile_foreign_constant(symbol, constant)
|
207
|
-
|
220
|
+
return if skip_foreign_constant?(symbol, constant)
|
221
|
+
return if seen?(symbol)
|
222
|
+
|
223
|
+
seen!(symbol)
|
224
|
+
|
225
|
+
scope = compile_scope(symbol, constant)
|
226
|
+
push_foreign_scope(symbol, constant, scope)
|
208
227
|
end
|
209
228
|
|
210
229
|
sig { params(symbol: String, constant: BasicObject).void.checked(:never) }
|
@@ -225,10 +244,9 @@ module Tapioca
|
|
225
244
|
def compile_alias(name, constant)
|
226
245
|
return if seen?(name)
|
227
246
|
|
228
|
-
|
247
|
+
seen!(name)
|
229
248
|
|
230
|
-
return if
|
231
|
-
return unless constant_in_gem?(name)
|
249
|
+
return if skip_alias?(name, constant)
|
232
250
|
|
233
251
|
target = name_of(constant)
|
234
252
|
# If target has no name, let's make it an anonymous class or module with `Class.new` or `Module.new`
|
@@ -247,10 +265,9 @@ module Tapioca
|
|
247
265
|
def compile_object(name, value)
|
248
266
|
return if seen?(name)
|
249
267
|
|
250
|
-
|
268
|
+
seen!(name)
|
251
269
|
|
252
|
-
return if
|
253
|
-
return unless constant_in_gem?(name)
|
270
|
+
return if skip_object?(name, value)
|
254
271
|
|
255
272
|
klass = class_of(value)
|
256
273
|
|
@@ -279,29 +296,29 @@ module Tapioca
|
|
279
296
|
@root << node
|
280
297
|
end
|
281
298
|
|
282
|
-
sig { params(name: String, constant: Module
|
283
|
-
def compile_module(name, constant
|
284
|
-
return
|
285
|
-
return if Tapioca::TypeVariableModule === constant
|
299
|
+
sig { params(name: String, constant: Module).void }
|
300
|
+
def compile_module(name, constant)
|
301
|
+
return if skip_module?(name, constant)
|
286
302
|
return if seen?(name)
|
287
303
|
|
288
|
-
|
304
|
+
seen!(name)
|
289
305
|
|
290
|
-
scope =
|
291
|
-
|
292
|
-
|
293
|
-
RBI::Class.new(name, superclass_name: superclass)
|
294
|
-
else
|
295
|
-
RBI::Module.new(name)
|
296
|
-
end
|
306
|
+
scope = compile_scope(name, constant)
|
307
|
+
push_scope(name, constant, scope)
|
308
|
+
end
|
297
309
|
|
298
|
-
|
299
|
-
|
310
|
+
sig { params(name: String, constant: Module).returns(RBI::Scope) }
|
311
|
+
def compile_scope(name, constant)
|
312
|
+
scope = if constant.is_a?(Class)
|
313
|
+
superclass = compile_superclass(constant)
|
314
|
+
RBI::Class.new(name, superclass_name: superclass)
|
300
315
|
else
|
301
|
-
|
316
|
+
RBI::Module.new(name)
|
302
317
|
end
|
303
318
|
|
304
319
|
@root << scope
|
320
|
+
|
321
|
+
scope
|
305
322
|
end
|
306
323
|
|
307
324
|
sig { params(constant: T::Class[T.anything]).returns(T.nilable(String)) }
|
@@ -353,6 +370,54 @@ module Tapioca
|
|
353
370
|
"::#{name}"
|
354
371
|
end
|
355
372
|
|
373
|
+
# Constants and properties filtering
|
374
|
+
|
375
|
+
sig { params(name: String).returns(T::Boolean) }
|
376
|
+
def skip_symbol?(name)
|
377
|
+
symbol_in_payload?(name) && !@bootstrap_symbols.include?(name)
|
378
|
+
end
|
379
|
+
|
380
|
+
sig { params(name: String, constant: T.anything).returns(T::Boolean).checked(:never) }
|
381
|
+
def skip_constant?(name, constant)
|
382
|
+
return true if name.strip.empty?
|
383
|
+
return true if name.start_with?("#<")
|
384
|
+
return true if name.downcase == name
|
385
|
+
return true if alias_namespaced?(name)
|
386
|
+
|
387
|
+
return true if T::Enum === constant # T::Enum instances are defined via `compile_enums`
|
388
|
+
|
389
|
+
false
|
390
|
+
end
|
391
|
+
|
392
|
+
sig { params(name: String, constant: Module).returns(T::Boolean) }
|
393
|
+
def skip_alias?(name, constant)
|
394
|
+
return true if symbol_in_payload?(name)
|
395
|
+
return true unless constant_in_gem?(name)
|
396
|
+
|
397
|
+
false
|
398
|
+
end
|
399
|
+
|
400
|
+
sig { params(name: String, constant: BasicObject).returns(T::Boolean).checked(:never) }
|
401
|
+
def skip_object?(name, constant)
|
402
|
+
return true if symbol_in_payload?(name)
|
403
|
+
return true unless constant_in_gem?(name)
|
404
|
+
|
405
|
+
false
|
406
|
+
end
|
407
|
+
|
408
|
+
sig { params(name: String, constant: Module).returns(T::Boolean) }
|
409
|
+
def skip_foreign_constant?(name, constant)
|
410
|
+
Tapioca::TypeVariableModule === constant
|
411
|
+
end
|
412
|
+
|
413
|
+
sig { params(name: String, constant: Module).returns(T::Boolean) }
|
414
|
+
def skip_module?(name, constant)
|
415
|
+
return true unless defined_in_gem?(constant, strict: false)
|
416
|
+
return true if Tapioca::TypeVariableModule === constant
|
417
|
+
|
418
|
+
false
|
419
|
+
end
|
420
|
+
|
356
421
|
sig { params(constant: Module, strict: T::Boolean).returns(T::Boolean) }
|
357
422
|
def defined_in_gem?(constant, strict: true)
|
358
423
|
files = get_file_candidates(constant)
|
@@ -385,7 +450,7 @@ module Tapioca
|
|
385
450
|
end
|
386
451
|
|
387
452
|
sig { params(name: String).void }
|
388
|
-
def
|
453
|
+
def seen!(name)
|
389
454
|
@seen.add(name)
|
390
455
|
end
|
391
456
|
|
@@ -394,6 +459,8 @@ module Tapioca
|
|
394
459
|
@seen.include?(name)
|
395
460
|
end
|
396
461
|
|
462
|
+
# Helpers
|
463
|
+
|
397
464
|
sig { params(constant: T.all(Module, T::Generic)).returns(String) }
|
398
465
|
def generic_name_of(constant)
|
399
466
|
type_name = T.must(constant.name)
|
data/lib/tapioca/gemfile.rb
CHANGED
@@ -156,6 +156,11 @@ module Tapioca
|
|
156
156
|
@spec.name
|
157
157
|
end
|
158
158
|
|
159
|
+
sig { returns(T::Array[::Gem::Dependency]) }
|
160
|
+
def dependencies
|
161
|
+
@spec.dependencies
|
162
|
+
end
|
163
|
+
|
159
164
|
sig { returns(String) }
|
160
165
|
def rbi_file_name
|
161
166
|
"#{name}@#{version}.rbi"
|
@@ -230,13 +235,14 @@ module Tapioca
|
|
230
235
|
|
231
236
|
sig { returns(Regexp) }
|
232
237
|
def require_paths_prefix_matcher
|
233
|
-
@require_paths_prefix_matcher
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
238
|
+
@require_paths_prefix_matcher ||= T.let(
|
239
|
+
begin
|
240
|
+
require_paths = T.unsafe(@spec).require_paths
|
241
|
+
prefix_matchers = require_paths.map { |rp| Regexp.new("^#{rp}/") }
|
242
|
+
Regexp.union(prefix_matchers)
|
243
|
+
end,
|
244
|
+
T.nilable(Regexp),
|
245
|
+
)
|
240
246
|
end
|
241
247
|
|
242
248
|
sig { params(file: String).returns(Pathname) }
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class GitAttributes
|
5
|
+
class << self
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(path: Pathname).void }
|
9
|
+
def create_generated_attribute_file(path)
|
10
|
+
create_gitattributes_file(path, <<~CONTENT)
|
11
|
+
**/*.rbi linguist-generated=true
|
12
|
+
CONTENT
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { params(path: Pathname).void }
|
16
|
+
def create_vendored_attribute_file(path)
|
17
|
+
create_gitattributes_file(path, <<~CONTENT)
|
18
|
+
**/*.rbi linguist-vendored=true
|
19
|
+
CONTENT
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
sig { params(path: Pathname, content: String).void }
|
25
|
+
def create_gitattributes_file(path, content)
|
26
|
+
# We don't want to start creating folders, just to write
|
27
|
+
# the `.gitattributes` file. So, if the folder doesn't
|
28
|
+
# exist, we just return.
|
29
|
+
return unless path.exist?
|
30
|
+
|
31
|
+
File.write(path.join(".gitattributes"), content)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -24,12 +24,12 @@ module Tapioca
|
|
24
24
|
::Gem::Requirement.new(selector).satisfied_by?(ActiveSupport.gem_version)
|
25
25
|
end
|
26
26
|
|
27
|
-
sig { params(src: String).returns(String) }
|
28
|
-
def template(src)
|
27
|
+
sig { params(src: String, trim_mode: String).returns(String) }
|
28
|
+
def template(src, trim_mode: ">")
|
29
29
|
erb = if ERB_SUPPORTS_KVARGS
|
30
|
-
::ERB.new(src, trim_mode:
|
30
|
+
::ERB.new(src, trim_mode: trim_mode)
|
31
31
|
else
|
32
|
-
::ERB.new(src, nil,
|
32
|
+
::ERB.new(src, nil, trim_mode)
|
33
33
|
end
|
34
34
|
|
35
35
|
erb.result(binding)
|