tapioca 0.5.3 → 0.6.0
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 +114 -23
- data/lib/tapioca/cli.rb +188 -65
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +94 -8
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +5 -4
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +1 -1
- data/lib/tapioca/compilers/dsl/active_record_relations.rb +703 -0
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +43 -13
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +39 -33
- data/lib/tapioca/compilers/dsl/base.rb +26 -42
- data/lib/tapioca/compilers/dsl/extensions/frozen_record.rb +29 -0
- data/lib/tapioca/compilers/dsl/frozen_record.rb +37 -0
- data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +27 -0
- data/lib/tapioca/compilers/dsl/identity_cache.rb +0 -1
- data/lib/tapioca/compilers/dsl/param_helper.rb +52 -0
- data/lib/tapioca/compilers/dsl/rails_generators.rb +120 -0
- data/lib/tapioca/compilers/dsl_compiler.rb +34 -6
- data/lib/tapioca/compilers/sorbet.rb +2 -0
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +51 -48
- data/lib/tapioca/executor.rb +79 -0
- data/lib/tapioca/gemfile.rb +28 -4
- data/lib/tapioca/generators/base.rb +11 -18
- data/lib/tapioca/generators/dsl.rb +33 -38
- data/lib/tapioca/generators/gem.rb +64 -34
- data/lib/tapioca/generators/init.rb +41 -16
- data/lib/tapioca/generators/todo.rb +6 -6
- data/lib/tapioca/helpers/cli_helper.rb +26 -0
- data/lib/tapioca/helpers/config_helper.rb +84 -0
- data/lib/tapioca/helpers/test/content.rb +51 -0
- data/lib/tapioca/helpers/test/isolation.rb +125 -0
- data/lib/tapioca/helpers/test/template.rb +34 -0
- data/lib/tapioca/internal.rb +3 -2
- data/lib/tapioca/rbi_ext/model.rb +13 -10
- data/lib/tapioca/reflection.rb +13 -0
- data/lib/tapioca/trackers/autoload.rb +70 -0
- data/lib/tapioca/trackers/constant_definition.rb +42 -0
- data/lib/tapioca/trackers/mixin.rb +78 -0
- data/lib/tapioca/trackers.rb +14 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +28 -2
- metadata +37 -13
- data/lib/tapioca/config.rb +0 -45
- data/lib/tapioca/config_builder.rb +0 -73
- data/lib/tapioca/constant_locator.rb +0 -34
@@ -7,6 +7,8 @@ rescue LoadError
|
|
7
7
|
return
|
8
8
|
end
|
9
9
|
|
10
|
+
require "tapioca/compilers/dsl/helper/active_record_constants"
|
11
|
+
|
10
12
|
module Tapioca
|
11
13
|
module Compilers
|
12
14
|
module Dsl
|
@@ -42,6 +44,7 @@ module Tapioca
|
|
42
44
|
# ~~~
|
43
45
|
class ActiveRecordScope < Base
|
44
46
|
extend T::Sig
|
47
|
+
include Helper::ActiveRecordConstants
|
45
48
|
|
46
49
|
sig do
|
47
50
|
override.params(
|
@@ -50,19 +53,33 @@ module Tapioca
|
|
50
53
|
).void
|
51
54
|
end
|
52
55
|
def decorate(root, constant)
|
53
|
-
|
54
|
-
|
56
|
+
method_names = scope_method_names(constant)
|
57
|
+
|
58
|
+
return if method_names.empty?
|
55
59
|
|
56
60
|
root.create_path(constant) do |model|
|
57
|
-
|
61
|
+
relations_enabled = generator_enabled?("ActiveRecordRelations")
|
62
|
+
|
63
|
+
relation_methods_module = model.create_module(RelationMethodsModuleName)
|
64
|
+
assoc_relation_methods_mod = model.create_module(AssociationRelationMethodsModuleName) if relations_enabled
|
65
|
+
|
66
|
+
method_names.each do |scope_method|
|
67
|
+
generate_scope_method(
|
68
|
+
relation_methods_module,
|
69
|
+
scope_method.to_s,
|
70
|
+
relations_enabled ? RelationClassName : "T.untyped"
|
71
|
+
)
|
58
72
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
73
|
+
next unless relations_enabled
|
74
|
+
|
75
|
+
generate_scope_method(
|
76
|
+
assoc_relation_methods_mod,
|
77
|
+
scope_method.to_s,
|
78
|
+
AssociationRelationClassName
|
79
|
+
)
|
63
80
|
end
|
64
81
|
|
65
|
-
model.create_extend(
|
82
|
+
model.create_extend(RelationMethodsModuleName)
|
66
83
|
end
|
67
84
|
end
|
68
85
|
|
@@ -73,16 +90,29 @@ module Tapioca
|
|
73
90
|
|
74
91
|
private
|
75
92
|
|
93
|
+
sig { params(constant: T.class_of(::ActiveRecord::Base)).returns(T::Array[Symbol]) }
|
94
|
+
def scope_method_names(constant)
|
95
|
+
scope_methods = T.let([], T::Array[Symbol])
|
96
|
+
|
97
|
+
# Keep gathering scope methods until we hit "ActiveRecord::Base"
|
98
|
+
until constant == ActiveRecord::Base
|
99
|
+
scope_methods.concat(constant.send(:generated_relation_methods).instance_methods(false))
|
100
|
+
|
101
|
+
# we are guaranteed to have a superclass that is of type "ActiveRecord::Base"
|
102
|
+
constant = T.cast(constant.superclass, T.class_of(ActiveRecord::Base))
|
103
|
+
end
|
104
|
+
|
105
|
+
scope_methods
|
106
|
+
end
|
107
|
+
|
76
108
|
sig do
|
77
109
|
params(
|
78
|
-
scope_method: String,
|
79
110
|
mod: RBI::Scope,
|
111
|
+
scope_method: String,
|
112
|
+
return_type: String
|
80
113
|
).void
|
81
114
|
end
|
82
|
-
def generate_scope_method(scope_method,
|
83
|
-
# This return type should actually be Model::ActiveRecord_Relation
|
84
|
-
return_type = "T.untyped"
|
85
|
-
|
115
|
+
def generate_scope_method(mod, scope_method, return_type)
|
86
116
|
mod.create_method(
|
87
117
|
scope_method,
|
88
118
|
parameters: [
|
@@ -34,53 +34,57 @@ module Tapioca
|
|
34
34
|
# # post.rbi
|
35
35
|
# # typed: true
|
36
36
|
# class Post
|
37
|
-
#
|
38
|
-
# def review_date=(review_date); end
|
37
|
+
# include StoreAccessors
|
39
38
|
#
|
40
|
-
#
|
41
|
-
#
|
39
|
+
# module StoreAccessors
|
40
|
+
# sig { params(review_date: T.nilable(Date)).returns(T.nilable(Date)) }
|
41
|
+
# def review_date=(review_date); end
|
42
42
|
#
|
43
|
-
#
|
44
|
-
#
|
43
|
+
# sig { returns(T.nilable(Date)) }
|
44
|
+
# def review_date; end
|
45
45
|
#
|
46
|
-
#
|
47
|
-
#
|
46
|
+
# sig { returns(T.nilable(Date)) }
|
47
|
+
# def review_date_was; end
|
48
48
|
#
|
49
|
-
#
|
50
|
-
#
|
49
|
+
# sig { returns(T::Boolean) }
|
50
|
+
# def review_date_changed?; end
|
51
51
|
#
|
52
|
-
#
|
53
|
-
#
|
52
|
+
# sig { returns(T.nilable(Date)) }
|
53
|
+
# def review_date_before_last_save; end
|
54
54
|
#
|
55
|
-
#
|
56
|
-
#
|
55
|
+
# sig { returns(T::Boolean) }
|
56
|
+
# def saved_change_to_review_date?; end
|
57
57
|
#
|
58
|
-
#
|
59
|
-
#
|
58
|
+
# sig { returns(T.nilable([T.nilable(Date), T.nilable(Date)])) }
|
59
|
+
# def review_date_change; end
|
60
60
|
#
|
61
|
-
#
|
62
|
-
#
|
61
|
+
# sig { returns(T.nilable([T.nilable(Date), T.nilable(Date)])) }
|
62
|
+
# def saved_change_to_review_date; end
|
63
63
|
#
|
64
|
-
#
|
65
|
-
#
|
64
|
+
# sig { params(reviewd: T::Boolean).returns(T::Boolean) }
|
65
|
+
# def reviewed=(reviewed); end
|
66
66
|
#
|
67
|
-
#
|
68
|
-
#
|
67
|
+
# sig { returns(T::Boolean) }
|
68
|
+
# def reviewed; end
|
69
69
|
#
|
70
|
-
#
|
71
|
-
#
|
70
|
+
# sig { returns(T::Boolean) }
|
71
|
+
# def reviewed_was; end
|
72
72
|
#
|
73
|
-
#
|
74
|
-
#
|
73
|
+
# sig { returns(T::Boolean) }
|
74
|
+
# def reviewed_changed?; end
|
75
75
|
#
|
76
|
-
#
|
77
|
-
#
|
76
|
+
# sig { returns(T::Boolean) }
|
77
|
+
# def reviewed_before_last_save; end
|
78
78
|
#
|
79
|
-
#
|
80
|
-
#
|
79
|
+
# sig { returns(T::Boolean) }
|
80
|
+
# def saved_change_to_reviewed?; end
|
81
81
|
#
|
82
|
-
#
|
83
|
-
#
|
82
|
+
# sig { returns(T.nilable([T::Boolean, T::Boolean])) }
|
83
|
+
# def reviewed_change; end
|
84
|
+
#
|
85
|
+
# sig { returns(T.nilable([T::Boolean, T::Boolean])) }
|
86
|
+
# def saved_change_to_reviewed; end
|
87
|
+
# end
|
84
88
|
# end
|
85
89
|
# ~~~
|
86
90
|
class ActiveRecordTypedStore < Base
|
@@ -105,7 +109,9 @@ module Tapioca
|
|
105
109
|
type = type_for(field.type_sym)
|
106
110
|
type = "T.nilable(#{type})" if field.null && type != "T.untyped"
|
107
111
|
|
108
|
-
|
112
|
+
store_accessors_module = model.create_module("StoreAccessors")
|
113
|
+
generate_methods(store_accessors_module, field.name.to_s, type)
|
114
|
+
model.create_include("StoreAccessors")
|
109
115
|
end
|
110
116
|
end
|
111
117
|
end
|
@@ -2,11 +2,13 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "tapioca/rbi_ext/model"
|
5
|
+
require "tapioca/compilers/dsl/param_helper"
|
6
|
+
require "tapioca/compilers/dsl_compiler"
|
5
7
|
|
6
8
|
module Tapioca
|
7
9
|
module Compilers
|
8
10
|
module Dsl
|
9
|
-
|
11
|
+
DSL_COMPILERS_DIR = T.let(File.expand_path("..", __FILE__).to_s, String)
|
10
12
|
|
11
13
|
class Base
|
12
14
|
extend T::Sig
|
@@ -22,9 +24,24 @@ module Tapioca
|
|
22
24
|
sig { returns(T::Array[String]) }
|
23
25
|
attr_reader :errors
|
24
26
|
|
25
|
-
sig {
|
26
|
-
def
|
27
|
+
sig { params(name: String).returns(T.nilable(T.class_of(Tapioca::Compilers::Dsl::Base))) }
|
28
|
+
def self.resolve(name)
|
29
|
+
# Try to find built-in tapioca generator first, then globally defined generator.
|
30
|
+
potentials = ["Tapioca::Compilers::Dsl::#{name}", name].map do |potential_name|
|
31
|
+
Object.const_get(potential_name)
|
32
|
+
rescue NameError
|
33
|
+
# Skip if we can't find generator by the potential name
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
potentials.compact.first
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { params(compiler: Tapioca::Compilers::DslCompiler).void }
|
41
|
+
def initialize(compiler)
|
42
|
+
@compiler = compiler
|
27
43
|
@processable_constants = T.let(Set.new(gather_constants), T::Set[Module])
|
44
|
+
@processable_constants.compare_by_identity
|
28
45
|
@errors = T.let([], T::Array[String])
|
29
46
|
end
|
30
47
|
|
@@ -33,6 +50,11 @@ module Tapioca
|
|
33
50
|
processable_constants.include?(constant)
|
34
51
|
end
|
35
52
|
|
53
|
+
sig { params(generator_name: String).returns(T::Boolean) }
|
54
|
+
def generator_enabled?(generator_name)
|
55
|
+
@compiler.generator_enabled?(generator_name)
|
56
|
+
end
|
57
|
+
|
36
58
|
sig do
|
37
59
|
abstract
|
38
60
|
.type_parameters(:T)
|
@@ -106,45 +128,7 @@ module Tapioca
|
|
106
128
|
)
|
107
129
|
end
|
108
130
|
|
109
|
-
|
110
|
-
def create_param(name, type:)
|
111
|
-
create_typed_param(RBI::Param.new(name), type)
|
112
|
-
end
|
113
|
-
|
114
|
-
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
115
|
-
def create_opt_param(name, type:, default:)
|
116
|
-
create_typed_param(RBI::OptParam.new(name, default), type)
|
117
|
-
end
|
118
|
-
|
119
|
-
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
120
|
-
def create_rest_param(name, type:)
|
121
|
-
create_typed_param(RBI::RestParam.new(name), type)
|
122
|
-
end
|
123
|
-
|
124
|
-
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
125
|
-
def create_kw_param(name, type:)
|
126
|
-
create_typed_param(RBI::KwParam.new(name), type)
|
127
|
-
end
|
128
|
-
|
129
|
-
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
130
|
-
def create_kw_opt_param(name, type:, default:)
|
131
|
-
create_typed_param(RBI::KwOptParam.new(name, default), type)
|
132
|
-
end
|
133
|
-
|
134
|
-
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
135
|
-
def create_kw_rest_param(name, type:)
|
136
|
-
create_typed_param(RBI::KwRestParam.new(name), type)
|
137
|
-
end
|
138
|
-
|
139
|
-
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
140
|
-
def create_block_param(name, type:)
|
141
|
-
create_typed_param(RBI::BlockParam.new(name), type)
|
142
|
-
end
|
143
|
-
|
144
|
-
sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
|
145
|
-
def create_typed_param(param, type)
|
146
|
-
RBI::TypedParam.new(param: param, type: type)
|
147
|
-
end
|
131
|
+
include ParamHelper
|
148
132
|
|
149
133
|
sig { params(method_def: T.any(Method, UnboundMethod)).returns(T::Array[RBI::TypedParam]) }
|
150
134
|
def compile_method_parameters_to_rbi(method_def)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "frozen_record"
|
6
|
+
rescue LoadError
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
module Tapioca
|
11
|
+
module Compilers
|
12
|
+
module Dsl
|
13
|
+
module Extensions
|
14
|
+
module FrozenRecord
|
15
|
+
attr_reader :__tapioca_scope_names
|
16
|
+
|
17
|
+
def scope(name, body)
|
18
|
+
@__tapioca_scope_names ||= []
|
19
|
+
@__tapioca_scope_names << name
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
::FrozenRecord::Base.singleton_class.prepend(self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -81,6 +81,8 @@ module Tapioca
|
|
81
81
|
end
|
82
82
|
|
83
83
|
record.create_include(module_name)
|
84
|
+
|
85
|
+
decorate_scopes(constant, record)
|
84
86
|
end
|
85
87
|
end
|
86
88
|
|
@@ -88,6 +90,41 @@ module Tapioca
|
|
88
90
|
def gather_constants
|
89
91
|
descendants_of(::FrozenRecord::Base).reject(&:abstract_class?)
|
90
92
|
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
sig { params(constant: T.class_of(::FrozenRecord::Base), record: RBI::Scope).void }
|
97
|
+
def decorate_scopes(constant, record)
|
98
|
+
scopes = T.unsafe(constant).__tapioca_scope_names
|
99
|
+
return if scopes.nil?
|
100
|
+
|
101
|
+
module_name = "GeneratedRelationMethods"
|
102
|
+
|
103
|
+
record.create_module(module_name) do |mod|
|
104
|
+
scopes.each do |name|
|
105
|
+
generate_scope_method(name.to_s, mod)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
record.create_extend(module_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
sig do
|
113
|
+
params(
|
114
|
+
scope_method: String,
|
115
|
+
mod: RBI::Scope,
|
116
|
+
).void
|
117
|
+
end
|
118
|
+
def generate_scope_method(scope_method, mod)
|
119
|
+
mod.create_method(
|
120
|
+
scope_method,
|
121
|
+
parameters: [
|
122
|
+
create_rest_param("args", type: "T.untyped"),
|
123
|
+
create_block_param("blk", type: "T.untyped"),
|
124
|
+
],
|
125
|
+
return_type: "T.untyped",
|
126
|
+
)
|
127
|
+
end
|
91
128
|
end
|
92
129
|
end
|
93
130
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Compilers
|
6
|
+
module Dsl
|
7
|
+
module Helper
|
8
|
+
module ActiveRecordConstants
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
AttributeMethodsModuleName = T.let("GeneratedAttributeMethods", String)
|
12
|
+
AssociationMethodsModuleName = T.let("GeneratedAssociationMethods", String)
|
13
|
+
|
14
|
+
RelationMethodsModuleName = T.let("GeneratedRelationMethods", String)
|
15
|
+
AssociationRelationMethodsModuleName = T.let("GeneratedAssociationRelationMethods", String)
|
16
|
+
CommonRelationMethodsModuleName = T.let("CommonRelationMethods", String)
|
17
|
+
|
18
|
+
RelationClassName = T.let("PrivateRelation", String)
|
19
|
+
RelationWhereChainClassName = T.let("PrivateRelationWhereChain", String)
|
20
|
+
AssociationRelationClassName = T.let("PrivateAssociationRelation", String)
|
21
|
+
AssociationRelationWhereChainClassName = T.let("PrivateAssociationRelationWhereChain", String)
|
22
|
+
AssociationsCollectionProxyClassName = T.let("PrivateCollectionProxy", String)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Compilers
|
6
|
+
module Dsl
|
7
|
+
module ParamHelper
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
11
|
+
def create_param(name, type:)
|
12
|
+
create_typed_param(RBI::Param.new(name), type)
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
16
|
+
def create_opt_param(name, type:, default:)
|
17
|
+
create_typed_param(RBI::OptParam.new(name, default), type)
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
21
|
+
def create_rest_param(name, type:)
|
22
|
+
create_typed_param(RBI::RestParam.new(name), type)
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
26
|
+
def create_kw_param(name, type:)
|
27
|
+
create_typed_param(RBI::KwParam.new(name), type)
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { params(name: String, type: String, default: String).returns(RBI::TypedParam) }
|
31
|
+
def create_kw_opt_param(name, type:, default:)
|
32
|
+
create_typed_param(RBI::KwOptParam.new(name, default), type)
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
36
|
+
def create_kw_rest_param(name, type:)
|
37
|
+
create_typed_param(RBI::KwRestParam.new(name), type)
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { params(name: String, type: String).returns(RBI::TypedParam) }
|
41
|
+
def create_block_param(name, type:)
|
42
|
+
create_typed_param(RBI::BlockParam.new(name), type)
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { params(param: RBI::Param, type: String).returns(RBI::TypedParam) }
|
46
|
+
def create_typed_param(param, type)
|
47
|
+
RBI::TypedParam.new(param: param, type: type)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "rails/generators"
|
6
|
+
require "rails/generators/app_base"
|
7
|
+
rescue LoadError
|
8
|
+
return
|
9
|
+
end
|
10
|
+
|
11
|
+
module Tapioca
|
12
|
+
module Compilers
|
13
|
+
module Dsl
|
14
|
+
# `Tapioca::Compilers::Dsl::RailsGenerators` generates RBI files for Rails generators
|
15
|
+
#
|
16
|
+
# For example, with the following generator:
|
17
|
+
#
|
18
|
+
# ~~~rb
|
19
|
+
# # lib/generators/sample_generator.rb
|
20
|
+
# class ServiceGenerator < Rails::Generators::NamedBase
|
21
|
+
# argument :result_type, type: :string
|
22
|
+
#
|
23
|
+
# class_option :skip_comments, type: :boolean, default: false
|
24
|
+
# end
|
25
|
+
# ~~~
|
26
|
+
#
|
27
|
+
# this compiler will produce the RBI file `service_generator.rbi` with the following content:
|
28
|
+
#
|
29
|
+
# ~~~rbi
|
30
|
+
# # service_generator.rbi
|
31
|
+
# # typed: strong
|
32
|
+
#
|
33
|
+
# class ServiceGenerator
|
34
|
+
# sig { returns(::String)}
|
35
|
+
# def result_type; end
|
36
|
+
#
|
37
|
+
# sig { returns(T::Boolean)}
|
38
|
+
# def skip_comments; end
|
39
|
+
# end
|
40
|
+
# ~~~
|
41
|
+
class RailsGenerators < Base
|
42
|
+
extend T::Sig
|
43
|
+
|
44
|
+
BUILT_IN_MATCHER = T.let(
|
45
|
+
/::(ActionMailbox|ActionText|ActiveRecord|Rails)::Generators/,
|
46
|
+
Regexp
|
47
|
+
)
|
48
|
+
|
49
|
+
sig { override.params(root: RBI::Tree, constant: T.class_of(::Rails::Generators::Base)).void }
|
50
|
+
def decorate(root, constant)
|
51
|
+
base_class = base_class_for(constant)
|
52
|
+
arguments = constant.arguments - base_class.arguments
|
53
|
+
class_options = constant.class_options.reject do |name, option|
|
54
|
+
base_class.class_options[name] == option
|
55
|
+
end
|
56
|
+
|
57
|
+
return if arguments.empty? && class_options.empty?
|
58
|
+
|
59
|
+
root.create_path(constant) do |klass|
|
60
|
+
arguments.each { |argument| generate_methods_for_argument(klass, argument) }
|
61
|
+
class_options.each { |_name, option| generate_methods_for_argument(klass, option) }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { override.returns(T::Enumerable[Module]) }
|
66
|
+
def gather_constants
|
67
|
+
all_modules.select do |const|
|
68
|
+
name = qualified_name_of(const)
|
69
|
+
|
70
|
+
name &&
|
71
|
+
!name.match?(BUILT_IN_MATCHER) &&
|
72
|
+
const < ::Rails::Generators::Base
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
sig { params(klass: RBI::Tree, argument: T.any(Thor::Argument, Thor::Option)).void }
|
79
|
+
def generate_methods_for_argument(klass, argument)
|
80
|
+
klass.create_method(
|
81
|
+
argument.name,
|
82
|
+
parameters: [],
|
83
|
+
return_type: type_for(argument)
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
sig do
|
88
|
+
params(constant: T.class_of(::Rails::Generators::Base))
|
89
|
+
.returns(T.class_of(::Rails::Generators::Base))
|
90
|
+
end
|
91
|
+
def base_class_for(constant)
|
92
|
+
ancestor = inherited_ancestors_of(constant).find do |klass|
|
93
|
+
qualified_name_of(klass)&.match?(BUILT_IN_MATCHER)
|
94
|
+
end
|
95
|
+
|
96
|
+
T.cast(ancestor, T.class_of(::Rails::Generators::Base))
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { params(arg: T.any(Thor::Argument, Thor::Option)).returns(String) }
|
100
|
+
def type_for(arg)
|
101
|
+
type =
|
102
|
+
case arg.type
|
103
|
+
when :array then "T::Array[::String]"
|
104
|
+
when :boolean then "T::Boolean"
|
105
|
+
when :hash then "T::Hash[::String, ::String]"
|
106
|
+
when :numeric then "::Numeric"
|
107
|
+
when :string then "::String"
|
108
|
+
else "T.untyped"
|
109
|
+
end
|
110
|
+
|
111
|
+
if arg.required || arg.default
|
112
|
+
type
|
113
|
+
else
|
114
|
+
"T.nilable(#{type})"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -22,21 +22,35 @@ module Tapioca
|
|
22
22
|
requested_constants: T::Array[Module],
|
23
23
|
requested_generators: T::Array[T.class_of(Dsl::Base)],
|
24
24
|
excluded_generators: T::Array[T.class_of(Dsl::Base)],
|
25
|
-
error_handler: T.
|
25
|
+
error_handler: T.proc.params(error: String).void,
|
26
|
+
number_of_workers: T.nilable(Integer),
|
26
27
|
).void
|
27
28
|
end
|
28
|
-
def initialize(
|
29
|
+
def initialize(
|
30
|
+
requested_constants:,
|
31
|
+
requested_generators: [],
|
32
|
+
excluded_generators: [],
|
33
|
+
error_handler: $stderr.method(:puts).to_proc,
|
34
|
+
number_of_workers: nil
|
35
|
+
)
|
29
36
|
@generators = T.let(
|
30
37
|
gather_generators(requested_generators, excluded_generators),
|
31
38
|
T::Enumerable[Dsl::Base]
|
32
39
|
)
|
33
40
|
@requested_constants = requested_constants
|
34
|
-
@error_handler =
|
41
|
+
@error_handler = error_handler
|
42
|
+
@number_of_workers = number_of_workers
|
35
43
|
end
|
36
44
|
|
37
|
-
sig
|
45
|
+
sig do
|
46
|
+
type_parameters(:T).params(
|
47
|
+
blk: T.proc.params(constant: Module, rbi: RBI::File).returns(T.type_parameter(:T))
|
48
|
+
).returns(T::Array[T.type_parameter(:T)])
|
49
|
+
end
|
38
50
|
def run(&blk)
|
39
51
|
constants_to_process = gather_constants(requested_constants)
|
52
|
+
.select { |c| Reflection.name_of(c) && Module === c } # Filter anonymous or value constants
|
53
|
+
.sort_by! { |c| T.must(Reflection.name_of(c)) }
|
40
54
|
|
41
55
|
if constants_to_process.empty?
|
42
56
|
report_error(<<~ERROR)
|
@@ -45,7 +59,10 @@ module Tapioca
|
|
45
59
|
ERROR
|
46
60
|
end
|
47
61
|
|
48
|
-
|
62
|
+
result = Executor.new(
|
63
|
+
constants_to_process,
|
64
|
+
number_of_workers: @number_of_workers
|
65
|
+
).run_in_parallel do |constant|
|
49
66
|
rbi = rbi_for_constant(constant)
|
50
67
|
next if rbi.nil?
|
51
68
|
|
@@ -55,6 +72,17 @@ module Tapioca
|
|
55
72
|
generators.flat_map(&:errors).each do |msg|
|
56
73
|
report_error(msg)
|
57
74
|
end
|
75
|
+
|
76
|
+
result.compact
|
77
|
+
end
|
78
|
+
|
79
|
+
sig { params(generator_name: String).returns(T::Boolean) }
|
80
|
+
def generator_enabled?(generator_name)
|
81
|
+
generator = Dsl::Base.resolve(generator_name)
|
82
|
+
|
83
|
+
return false unless generator
|
84
|
+
|
85
|
+
@generators.any?(generator)
|
58
86
|
end
|
59
87
|
|
60
88
|
private
|
@@ -71,7 +99,7 @@ module Tapioca
|
|
71
99
|
!excluded_generators.include?(klass)
|
72
100
|
end.sort_by { |klass| T.must(klass.name) }
|
73
101
|
|
74
|
-
generator_klasses.map(
|
102
|
+
generator_klasses.map { |generator_klass| generator_klass.new(self) }
|
75
103
|
end
|
76
104
|
|
77
105
|
sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
|
@@ -18,6 +18,8 @@ module Tapioca
|
|
18
18
|
EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
|
19
19
|
|
20
20
|
FEATURE_REQUIREMENTS = T.let({
|
21
|
+
# First tag that includes https://github.com/sorbet/sorbet/pull/4706
|
22
|
+
to_ary_nil_support: Gem::Requirement.new(">= 0.5.9220"),
|
21
23
|
}.freeze, T::Hash[Symbol, Gem::Requirement])
|
22
24
|
|
23
25
|
class << self
|