tapioca 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|