tapioca 0.11.8 → 0.11.10
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 +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)
|