tapioca 0.4.4 → 0.4.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -0
- data/README.md +15 -11
- data/Rakefile +1 -0
- data/lib/tapioca/cli.rb +8 -1
- data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +1 -0
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +9 -9
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +11 -0
- data/lib/tapioca/compilers/dsl/active_record_identity_cache.rb +3 -4
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +1 -1
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +0 -2
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +1 -1
- data/lib/tapioca/compilers/dsl/base.rb +14 -10
- data/lib/tapioca/compilers/dsl/protobuf.rb +8 -8
- data/lib/tapioca/compilers/dsl/url_helpers.rb +68 -0
- data/lib/tapioca/compilers/dsl_compiler.rb +5 -5
- data/lib/tapioca/compilers/requires_compiler.rb +34 -6
- data/lib/tapioca/compilers/sorbet.rb +1 -1
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +51 -23
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +1 -1
- data/lib/tapioca/compilers/symbol_table_compiler.rb +1 -1
- data/lib/tapioca/compilers/todos_compiler.rb +1 -1
- data/lib/tapioca/core_ext/class.rb +8 -3
- data/lib/tapioca/gemfile.rb +1 -1
- data/lib/tapioca/generator.rb +1 -1
- data/lib/tapioca/loader.rb +1 -1
- data/lib/tapioca/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aafcbad81626f19f1dd8dd93f7a781febc523582af0e8cc69913c46290f41241
|
4
|
+
data.tar.gz: 716d90aae514e148dbdf6431d8252ee183dbf2aba30d7284adba4d5b80dcb8bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56387ece08e56d0ae44486b4721fd3c13b74cecce488f90055da1f6fcfc3e5af3839f65b9bfe44f381962a31f39421f14f5d128e34a0c020cfcf14d167e9c93a
|
7
|
+
data.tar.gz: 96faf43ea930219c07b39ff9bf0b0e755c53344a73b6cb7d75fd055ad3b864e33e1b66e06b5086693a39eb175e04a7be737996aaddfe7a5cde88da75822a23e2
|
data/Gemfile
CHANGED
@@ -11,6 +11,7 @@ group(:deployment, :development) do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
gem("bundler", "~> 1.17")
|
14
|
+
gem("yard", "~> 0.9.25")
|
14
15
|
gem("pry-byebug")
|
15
16
|
gem("minitest")
|
16
17
|
gem("minitest-hooks")
|
@@ -33,3 +34,5 @@ group(:development, :test) do
|
|
33
34
|
gem("activeresource", "~> 5.1", require: false)
|
34
35
|
gem("google-protobuf", "~>3.12.0", require: false)
|
35
36
|
end
|
37
|
+
|
38
|
+
gem "rubocop-sorbet", ">= 0.4.1"
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Tapioca
|
2
2
|
|
3
|
-
|
3
|
+
![Build Status](https://github.com/Shopify/tapioca/workflows/CI/badge.svg)
|
4
4
|
|
5
5
|
Tapioca is a library used to generate RBI (Ruby interface) files for use with [Sorbet](https://sorbet.org). RBI files provide the structure (classes, modules, methods, parameters) of the gem/library to Sorbet to assist with typechecking.
|
6
6
|
|
@@ -55,6 +55,8 @@ and do not forget to execute `tapioca` using `bundler`:
|
|
55
55
|
```shell
|
56
56
|
$ bundle exec tapioca
|
57
57
|
Commands:
|
58
|
+
tapioca --version, -v # show version
|
59
|
+
tapioca dsl [constant...] # generate RBIs for dynamic methods
|
58
60
|
tapioca generate [gem...] # generate RBIs from gems
|
59
61
|
tapioca help [COMMAND] # Describe available commands or one specific command
|
60
62
|
tapioca init # initializes folder structure
|
@@ -63,12 +65,12 @@ Commands:
|
|
63
65
|
tapioca todo # generate the list of unresolved constants
|
64
66
|
|
65
67
|
Options:
|
66
|
-
--pre, -b, [--prerequire=file]
|
67
|
-
--post, -a, [--postrequire=file]
|
68
|
-
--out, -o, [--outdir=directory]
|
69
|
-
|
70
|
-
|
71
|
-
--typed, -t, [--typed-overrides=gem:level] # Overrides for typed sigils for generated gem RBIs
|
68
|
+
--pre, -b, [--prerequire=file] # A file to be required before Bundler.require is called
|
69
|
+
--post, -a, [--postrequire=file] # A file to be required after Bundler.require is called
|
70
|
+
--out, -o, [--outdir=directory] # The output directory for generated RBI files
|
71
|
+
--cmd, -c, [--generate-command=command] # The command to run to regenerate RBI files
|
72
|
+
-x, [--exclude=gem [gem ...]] # Excludes the given gem(s) from RBI generation
|
73
|
+
--typed, -t, [--typed-overrides=gem:level [gem:level ...]] # Overrides for typed sigils for generated gem RBIs
|
72
74
|
```
|
73
75
|
|
74
76
|
## Usage
|
@@ -97,6 +99,12 @@ Command: `tapioca todo`
|
|
97
99
|
|
98
100
|
This will generate the file `sorbet/rbi/todo.rbi` defining all unresolved constants as empty modules.
|
99
101
|
|
102
|
+
### Generate DSL RBI files
|
103
|
+
|
104
|
+
Command: `tapioca dsl [constant...]`
|
105
|
+
|
106
|
+
This will generate DSL RBIs for specified constants (or for all handled constants, if a constant name is not supplied). You can read about DSL RBI generators supplied by `tapioca` in [the manual](manual/generators.md).
|
107
|
+
|
100
108
|
### Flags
|
101
109
|
|
102
110
|
- `--prerequire [file]`: A file to be required before `Bundler.require` is called.
|
@@ -105,10 +113,6 @@ This will generate the file `sorbet/rbi/todo.rbi` defining all unresolved consta
|
|
105
113
|
- `--generate-command [command]`: The command to run to regenerate RBI files (used in header comment of the RBI files), defaults to the current command.
|
106
114
|
- `--typed-overrides [gem:level]`: Overrides typed sigils for generated gem RBIs for gem `gem` to level `level` (`level` can be one of `ignore`, `false`, `true`, `strict`, or `strong`, see [the Sorbet docs](https://sorbet.org/docs/static#file-level-granularity-strictness-levels) for more details).
|
107
115
|
|
108
|
-
### Strong typing option for ActiveRecord column methods
|
109
|
-
|
110
|
-
`tapioca` gives you the option to generate stricter type signatures for your ActiveRecord column types. By default, methods generated for columns that are defined in the schema have signatures of T.untyped. However, if the object extends a module with name StrongTypeGeneration, tapioca will generate stricter signatures that follow closely with the types defined in the schema. Expectation is the StrongTypeGeneration module you define in your application won't allow objects to be initialized with "bad state". It will check all the attributes that are not nillable to ensure they are not nil.
|
111
|
-
|
112
116
|
## Contributing
|
113
117
|
|
114
118
|
Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/tapioca. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://github.com/Shopify/tapioca/blob/master/CODE_OF_CONDUCT.md) code of conduct.
|
data/Rakefile
CHANGED
data/lib/tapioca/cli.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'thor'
|
5
5
|
|
@@ -34,6 +34,8 @@ module Tapioca
|
|
34
34
|
banner: "gem:level [gem:level ...]",
|
35
35
|
desc: "Overrides for typed sigils for generated gem RBIs"
|
36
36
|
|
37
|
+
map T.unsafe(%w[--version -v] => :__print_version)
|
38
|
+
|
37
39
|
desc "init", "initializes folder structure"
|
38
40
|
def init
|
39
41
|
create_file(Config::SORBET_CONFIG, skip: true) do
|
@@ -92,6 +94,11 @@ module Tapioca
|
|
92
94
|
end
|
93
95
|
end
|
94
96
|
|
97
|
+
desc "--version, -v", "show version"
|
98
|
+
def __print_version
|
99
|
+
puts "Tapioca v#{Tapioca::VERSION}"
|
100
|
+
end
|
101
|
+
|
95
102
|
no_commands do
|
96
103
|
def self.exit_on_failure?
|
97
104
|
true
|
@@ -45,10 +45,10 @@ module Tapioca
|
|
45
45
|
# sig { params(value: T.nilable(::User)).void }
|
46
46
|
# def author=(value); end
|
47
47
|
#
|
48
|
-
# sig { params(args: T.untyped, blk: T.untyped).returns(
|
48
|
+
# sig { params(args: T.untyped, blk: T.untyped).returns(::User) }
|
49
49
|
# def build_author(*args, &blk); end
|
50
50
|
#
|
51
|
-
# sig { params(args: T.untyped, blk: T.untyped).returns(
|
51
|
+
# sig { params(args: T.untyped, blk: T.untyped).returns(::Category) }
|
52
52
|
# def build_category(*args, &blk); end
|
53
53
|
#
|
54
54
|
# sig { returns(T.nilable(::Category)) }
|
@@ -69,16 +69,16 @@ module Tapioca
|
|
69
69
|
# sig { params(value: T::Enumerable[::Comment]).void }
|
70
70
|
# def comments=(value); end
|
71
71
|
#
|
72
|
-
# sig { params(args: T.untyped, blk: T.untyped).returns(
|
72
|
+
# sig { params(args: T.untyped, blk: T.untyped).returns(::User) }
|
73
73
|
# def create_author(*args, &blk); end
|
74
74
|
#
|
75
|
-
# sig { params(args: T.untyped, blk: T.untyped).returns(
|
75
|
+
# sig { params(args: T.untyped, blk: T.untyped).returns(::User) }
|
76
76
|
# def create_author!(*args, &blk); end
|
77
77
|
#
|
78
|
-
# sig { params(args: T.untyped, blk: T.untyped).returns(
|
78
|
+
# sig { params(args: T.untyped, blk: T.untyped).returns(::Category) }
|
79
79
|
# def create_category(*args, &blk); end
|
80
80
|
#
|
81
|
-
# sig { params(args: T.untyped, blk: T.untyped).returns(
|
81
|
+
# sig { params(args: T.untyped, blk: T.untyped).returns(::Category) }
|
82
82
|
# def create_category!(*args, &blk); end
|
83
83
|
#
|
84
84
|
# sig { returns(T.nilable(::User)) }
|
@@ -160,7 +160,7 @@ module Tapioca
|
|
160
160
|
Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
|
161
161
|
Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
|
162
162
|
],
|
163
|
-
return_type:
|
163
|
+
return_type: association_class
|
164
164
|
)
|
165
165
|
create_method(
|
166
166
|
klass,
|
@@ -169,7 +169,7 @@ module Tapioca
|
|
169
169
|
Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
|
170
170
|
Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
|
171
171
|
],
|
172
|
-
return_type:
|
172
|
+
return_type: association_class
|
173
173
|
)
|
174
174
|
create_method(
|
175
175
|
klass,
|
@@ -178,7 +178,7 @@ module Tapioca
|
|
178
178
|
Parlour::RbiGenerator::Parameter.new("*args", type: "T.untyped"),
|
179
179
|
Parlour::RbiGenerator::Parameter.new("&blk", type: "T.untyped"),
|
180
180
|
],
|
181
|
-
return_type:
|
181
|
+
return_type: association_class
|
182
182
|
)
|
183
183
|
end
|
184
184
|
end
|
@@ -17,6 +17,17 @@ module Tapioca
|
|
17
17
|
# responsible for defining the attribute methods that would be created for the columns that
|
18
18
|
# are defined in the Active Record model.
|
19
19
|
#
|
20
|
+
# **Note:** This generator, by default, generates weak signatures for column methods and treats each
|
21
|
+
# column to be `T.untyped`. This is done on purpose to ensure that the nilability of Active Record
|
22
|
+
# columns do not make it hard for existing code to adopt gradual typing. It is possible, however, to
|
23
|
+
# generate stricter type signatures for your ActiveRecord column types. If your ActiveRecord model extends
|
24
|
+
# a module with name `StrongTypeGeneration`, this generator will generate stricter signatures that follow
|
25
|
+
# closely with the types defined in the schema.
|
26
|
+
#
|
27
|
+
# The `StrongTypeGeneration` module you define in your application should add an `after_initialize` callback
|
28
|
+
# to the model and ensure that all the non-nilable attributes of the model are actually initialized with non-`nil`
|
29
|
+
# values.
|
30
|
+
#
|
20
31
|
# For example, with the following model class:
|
21
32
|
#
|
22
33
|
# ~~~rb
|
@@ -16,7 +16,7 @@ module Tapioca
|
|
16
16
|
module Compilers
|
17
17
|
module Dsl
|
18
18
|
# `Tapioca::Compilers::DSL::ActiveRecordIdentityCache` generates RBI files for ActiveRecord models
|
19
|
-
# that use `include IdentityCache
|
19
|
+
# that use `include IdentityCache`.
|
20
20
|
# `IdentityCache` is a blob level caching solution to plug into ActiveRecord. (see https://github.com/Shopify/identity_cache).
|
21
21
|
#
|
22
22
|
# For example, with the following ActiveRecord class:
|
@@ -61,7 +61,6 @@ module Tapioca
|
|
61
61
|
# def fetch_by_title_and_review_date(title, review_date, includes: nil); end
|
62
62
|
# end
|
63
63
|
# ~~~
|
64
|
-
|
65
64
|
class ActiveRecordIdentityCache < Base
|
66
65
|
extend T::Sig
|
67
66
|
|
@@ -109,7 +108,7 @@ module Tapioca
|
|
109
108
|
sig { override.returns(T::Enumerable[Module]) }
|
110
109
|
def gather_constants
|
111
110
|
::ActiveRecord::Base.descendants.select do |klass|
|
112
|
-
klass < IdentityCache
|
111
|
+
klass < IdentityCache::WithoutPrimaryIndex
|
113
112
|
end
|
114
113
|
end
|
115
114
|
|
@@ -127,7 +126,7 @@ module Tapioca
|
|
127
126
|
if returns_collection
|
128
127
|
COLLECTION_TYPE.call(cache_type)
|
129
128
|
else
|
130
|
-
"::#{cache_type}"
|
129
|
+
"T.nilable(::#{cache_type})"
|
131
130
|
end
|
132
131
|
rescue ArgumentError
|
133
132
|
"T.untyped"
|
@@ -37,7 +37,7 @@ module Tapioca
|
|
37
37
|
# module Post::GeneratedRelationMethods
|
38
38
|
# sig { params(args: T.untyped, blk: T.untyped).returns(T.untyped) }
|
39
39
|
# def private_kind(*args, &blk); end
|
40
|
-
|
40
|
+
#
|
41
41
|
# sig { params(args: T.untyped, blk: T.untyped).returns(T.untyped) }
|
42
42
|
# def public_kind(*args, &blk); end
|
43
43
|
# end
|
@@ -92,24 +92,28 @@ module Tapioca
|
|
92
92
|
method_def = signature.nil? ? method_def : signature.method
|
93
93
|
method_types = parameters_types_from_signature(method_def, signature)
|
94
94
|
|
95
|
-
method_def.parameters.each_with_index.map do |(type, name),
|
96
|
-
|
97
|
-
|
95
|
+
method_def.parameters.each_with_index.map do |(type, name), index|
|
96
|
+
fallback_arg_name = "_arg#{index}"
|
97
|
+
|
98
|
+
name ||= fallback_arg_name
|
99
|
+
name = name.to_s.gsub(/&|\*/, fallback_arg_name) # avoid incorrect names from `delegate`
|
100
|
+
method_type = method_types[index]
|
101
|
+
|
98
102
|
case type
|
99
103
|
when :req
|
100
|
-
::Parlour::RbiGenerator::Parameter.new(name, type:
|
104
|
+
::Parlour::RbiGenerator::Parameter.new(name, type: method_type)
|
101
105
|
when :opt
|
102
|
-
::Parlour::RbiGenerator::Parameter.new(name, type:
|
106
|
+
::Parlour::RbiGenerator::Parameter.new(name, type: method_type, default: 'T.unsafe(nil)')
|
103
107
|
when :rest
|
104
|
-
::Parlour::RbiGenerator::Parameter.new("*#{name}", type:
|
108
|
+
::Parlour::RbiGenerator::Parameter.new("*#{name}", type: method_type)
|
105
109
|
when :keyreq
|
106
|
-
::Parlour::RbiGenerator::Parameter.new("#{name}:", type:
|
110
|
+
::Parlour::RbiGenerator::Parameter.new("#{name}:", type: method_type)
|
107
111
|
when :key
|
108
|
-
::Parlour::RbiGenerator::Parameter.new("#{name}:", type:
|
112
|
+
::Parlour::RbiGenerator::Parameter.new("#{name}:", type: method_type, default: 'T.unsafe(nil)')
|
109
113
|
when :keyrest
|
110
|
-
::Parlour::RbiGenerator::Parameter.new("**#{name}", type:
|
114
|
+
::Parlour::RbiGenerator::Parameter.new("**#{name}", type: method_type)
|
111
115
|
when :block
|
112
|
-
::Parlour::RbiGenerator::Parameter.new("&#{name}", type:
|
116
|
+
::Parlour::RbiGenerator::Parameter.new("&#{name}", type: method_type)
|
113
117
|
else
|
114
118
|
raise "Unknown type `#{type}`."
|
115
119
|
end
|
@@ -12,19 +12,19 @@ module Tapioca
|
|
12
12
|
module Compilers
|
13
13
|
module Dsl
|
14
14
|
# `Tapioca::Compilers::Dsl::Protobuf` decorates RBI files for subclasses of
|
15
|
-
# `Google::Protobuf::MessageExts
|
16
|
-
# (see https://github.com/coinbase/protoc-gen-rbi).
|
15
|
+
# `Google::Protobuf::MessageExts` (see https://github.com/protocolbuffers/protobuf/tree/master/ruby).
|
17
16
|
#
|
18
17
|
# For example, with the following "cart.rb" file:
|
19
18
|
#
|
20
19
|
# ~~~rb
|
21
20
|
# Google::Protobuf::DescriptorPool.generated_pool.build do
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
21
|
+
# add_file("cart.proto", :syntax => :proto3) do
|
22
|
+
# add_message "MyCart" do
|
23
|
+
# optional :shop_id, :int32, 1
|
24
|
+
# optional :customer_id, :int64, 2
|
25
|
+
# optional :number_value, :double, 3
|
26
|
+
# optional :string_value, :string, 4
|
27
|
+
# end
|
28
28
|
# end
|
29
29
|
# end
|
30
30
|
# ~~~
|
@@ -14,6 +14,74 @@ end
|
|
14
14
|
module Tapioca
|
15
15
|
module Compilers
|
16
16
|
module Dsl
|
17
|
+
# `Tapioca::Compilers::Dsl::UrlHelpers` generates RBI files for classes that include or extend
|
18
|
+
# `Rails.application.routes.url_helpers`
|
19
|
+
# (see https://api.rubyonrails.org/v5.1.7/classes/ActionDispatch/Routing/UrlFor.html#module-ActionDispatch::Routing::UrlFor-label-URL+generation+for+named+routes).
|
20
|
+
#
|
21
|
+
# For example, with the following setup:
|
22
|
+
#
|
23
|
+
# ~~~rb
|
24
|
+
# # config/application.rb
|
25
|
+
# class Application < Rails::Application
|
26
|
+
# routes.draw do
|
27
|
+
# resource :index
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# ~~~
|
31
|
+
#
|
32
|
+
# ~~~rb
|
33
|
+
# app/models/post.rb
|
34
|
+
# class Post
|
35
|
+
# include Rails.application.routes.url_helpers
|
36
|
+
# end
|
37
|
+
# ~~~
|
38
|
+
#
|
39
|
+
# this generator will produce the following RBI files:
|
40
|
+
#
|
41
|
+
# ~~~rbi
|
42
|
+
# # generated_path_helpers_module.rbi
|
43
|
+
# # typed: true
|
44
|
+
# module GeneratedPathHelpersModule
|
45
|
+
# include ActionDispatch::Routing::PolymorphicRoutes
|
46
|
+
# include ActionDispatch::Routing::UrlFor
|
47
|
+
#
|
48
|
+
# sig { params(args: T.untyped).returns(String) }
|
49
|
+
# def edit_index_path(*args); end
|
50
|
+
#
|
51
|
+
# sig { params(args: T.untyped).returns(String) }
|
52
|
+
# def index_path(*args); end
|
53
|
+
#
|
54
|
+
# sig { params(args: T.untyped).returns(String) }
|
55
|
+
# def new_index_path(*args); end
|
56
|
+
# end
|
57
|
+
# ~~~
|
58
|
+
#
|
59
|
+
# ~~~rbi
|
60
|
+
# # generated_url_helpers_module.rbi
|
61
|
+
# # typed: true
|
62
|
+
# module GeneratedUrlHelpersModule
|
63
|
+
# include ActionDispatch::Routing::PolymorphicRoutes
|
64
|
+
# include ActionDispatch::Routing::UrlFor
|
65
|
+
#
|
66
|
+
# sig { params(args: T.untyped).returns(String) }
|
67
|
+
# def edit_index_url(*args); end
|
68
|
+
#
|
69
|
+
# sig { params(args: T.untyped).returns(String) }
|
70
|
+
# def index_url(*args); end
|
71
|
+
#
|
72
|
+
# sig { params(args: T.untyped).returns(String) }
|
73
|
+
# def new_index_url(*args); end
|
74
|
+
# end
|
75
|
+
# ~~~
|
76
|
+
#
|
77
|
+
# ~~~rbi
|
78
|
+
# # post.rbi
|
79
|
+
# # typed: true
|
80
|
+
# class Post
|
81
|
+
# include GeneratedPathHelpersModule
|
82
|
+
# include GeneratedUrlHelpersModule
|
83
|
+
# end
|
84
|
+
# ~~~
|
17
85
|
class UrlHelpers < Base
|
18
86
|
extend T::Sig
|
19
87
|
|
@@ -1,5 +1,5 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
|
-
# typed: true
|
3
3
|
|
4
4
|
require "tapioca/compilers/dsl/base"
|
5
5
|
|
@@ -30,7 +30,7 @@ module Tapioca
|
|
30
30
|
T::Enumerable[Dsl::Base]
|
31
31
|
)
|
32
32
|
@requested_constants = requested_constants
|
33
|
-
@error_handler = error_handler || $stderr.method(:puts)
|
33
|
+
@error_handler = T.let(error_handler || $stderr.method(:puts), T.proc.params(error: String).void)
|
34
34
|
end
|
35
35
|
|
36
36
|
sig { params(blk: T.proc.params(constant: Module, rbi: String).void).void }
|
@@ -54,9 +54,9 @@ module Tapioca
|
|
54
54
|
|
55
55
|
private
|
56
56
|
|
57
|
-
sig { params(requested_generators: T::Array[String]).returns(
|
57
|
+
sig { params(requested_generators: T::Array[String]).returns(T.proc.params(klass: Class).returns(T::Boolean)) }
|
58
58
|
def generator_filter(requested_generators)
|
59
|
-
return
|
59
|
+
return ->(_klass) { true } if requested_generators.empty?
|
60
60
|
|
61
61
|
generators = requested_generators.map(&:downcase)
|
62
62
|
|
@@ -70,7 +70,7 @@ module Tapioca
|
|
70
70
|
def gather_generators(requested_generators)
|
71
71
|
generator_filter = generator_filter(requested_generators)
|
72
72
|
|
73
|
-
Dsl::Base.descendants.select(&generator_filter).map(&:new)
|
73
|
+
T.cast(Dsl::Base.descendants.select(&generator_filter).map(&:new), T::Enumerable[Dsl::Base])
|
74
74
|
end
|
75
75
|
|
76
76
|
sig { params(requested_constants: T::Array[Module]).returns(T::Set[Module]) }
|
@@ -1,5 +1,5 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
1
|
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'spoom'
|
5
5
|
|
@@ -34,7 +34,8 @@ module Tapioca
|
|
34
34
|
path = (Pathname.new(@sorbet_path) / "../.." / path).cleanpath
|
35
35
|
if path.directory?
|
36
36
|
Dir.glob("#{path}/**/*.rb", File::FNM_EXTGLOB).reject do |file|
|
37
|
-
|
37
|
+
relative_file_path = Pathname.new(file).relative_path_from(path)
|
38
|
+
file_ignored_by_sorbet?(config, relative_file_path)
|
38
39
|
end
|
39
40
|
else
|
40
41
|
[path.to_s]
|
@@ -49,13 +50,40 @@ module Tapioca
|
|
49
50
|
end.compact
|
50
51
|
end
|
51
52
|
|
52
|
-
sig { params(config: Spoom::Sorbet::Config,
|
53
|
-
def file_ignored_by_sorbet?(config,
|
54
|
-
|
55
|
-
|
53
|
+
sig { params(config: Spoom::Sorbet::Config, file_path: Pathname).returns(T::Boolean) }
|
54
|
+
def file_ignored_by_sorbet?(config, file_path)
|
55
|
+
file_path_parts = path_parts(file_path)
|
56
|
+
|
57
|
+
config.ignore.any? do |ignore|
|
58
|
+
# Sorbet --ignore matching method:
|
59
|
+
# ---
|
60
|
+
# Ignores input files that contain the given
|
61
|
+
# string in their paths (relative to the input
|
62
|
+
# path passed to Sorbet).
|
63
|
+
#
|
64
|
+
# Strings beginning with / match against the
|
65
|
+
# prefix of these relative paths; others are
|
66
|
+
# substring matchs.
|
67
|
+
|
68
|
+
# Matches must be against whole folder and file
|
69
|
+
# names, so `foo` matches `/foo/bar.rb` and
|
70
|
+
# `/bar/foo/baz.rb` but not `/foo.rb` or
|
71
|
+
# `/foo2/bar.rb`.
|
72
|
+
ignore_parts = path_parts(Pathname.new(ignore))
|
73
|
+
file_path_part_sequences = file_path_parts.each_cons(ignore_parts.size)
|
74
|
+
# if ignore string begins with /, we only want the first sequence to match
|
75
|
+
file_path_part_sequences = [file_path_part_sequences.first].to_enum if ignore.start_with?("/")
|
76
|
+
|
77
|
+
# we need to match whole segments
|
78
|
+
file_path_part_sequences.include?(ignore_parts)
|
56
79
|
end
|
57
80
|
end
|
58
81
|
|
82
|
+
sig { params(path: Pathname).returns(T::Array[String]) }
|
83
|
+
def path_parts(path)
|
84
|
+
T.unsafe(path).descend.map { |part| part.basename.to_s }
|
85
|
+
end
|
86
|
+
|
59
87
|
sig { params(files: T::Enumerable[String], name: String).returns(T::Boolean) }
|
60
88
|
def name_in_project?(files, name)
|
61
89
|
files.any? do |file|
|
@@ -1,5 +1,5 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'pathname'
|
5
5
|
|
@@ -209,7 +209,11 @@ module Tapioca
|
|
209
209
|
method = "const" if prop.fetch(:immutable, false)
|
210
210
|
type = prop.fetch(:type_object, "T.untyped")
|
211
211
|
|
212
|
-
|
212
|
+
if prop.key?(:default)
|
213
|
+
indented("#{method} :#{name}, #{type}, default: T.unsafe(nil)")
|
214
|
+
else
|
215
|
+
indented("#{method} :#{name}, #{type}")
|
216
|
+
end
|
213
217
|
end.join("\n")
|
214
218
|
end
|
215
219
|
|
@@ -383,8 +387,18 @@ module Tapioca
|
|
383
387
|
indented("include(#{qualified_name_of(mod)})")
|
384
388
|
end.join("\n")
|
385
389
|
|
386
|
-
|
387
|
-
|
390
|
+
ancestors = singleton_class_of(constant).ancestors
|
391
|
+
extends_as_concern = ancestors.any? do |mod|
|
392
|
+
qualified_name_of(mod) == "::ActiveSupport::Concern"
|
393
|
+
end
|
394
|
+
class_methods_module = resolve_constant("#{name_of(constant)}::ClassMethods")
|
395
|
+
|
396
|
+
mixed_in_module = if extends_as_concern && Module === class_methods_module
|
397
|
+
class_methods_module
|
398
|
+
else
|
399
|
+
dynamic_extends.find do |mod|
|
400
|
+
mod != constant && public_module?(mod)
|
401
|
+
end
|
388
402
|
end
|
389
403
|
|
390
404
|
return result if mixed_in_module.nil?
|
@@ -494,15 +508,18 @@ module Tapioca
|
|
494
508
|
return if symbol_ignored?(symbol_name) && !method_in_gem?(method)
|
495
509
|
|
496
510
|
signature = signature_of(method)
|
497
|
-
method = signature.method if signature
|
511
|
+
method = T.let(signature.method, UnboundMethod) if signature
|
498
512
|
|
499
513
|
method_name = method.name.to_s
|
500
514
|
return unless valid_method_name?(method_name)
|
501
515
|
return if struct_method?(constant, method_name)
|
502
516
|
return if method_name.start_with?("__t_props_generated_")
|
503
517
|
|
504
|
-
|
505
|
-
|
518
|
+
parameters = T.let(method.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
|
519
|
+
|
520
|
+
sanitized_parameters = parameters.each_with_index.map do |(type, name), index|
|
521
|
+
fallback_arg_name = "_arg#{index}"
|
522
|
+
|
506
523
|
unless name
|
507
524
|
# For attr_writer methods, Sorbet signatures have the name
|
508
525
|
# of the method (without the trailing = sign) as the name of
|
@@ -512,22 +529,27 @@ module Tapioca
|
|
512
529
|
# method and the parameter is required and there is a single
|
513
530
|
# parameter and the signature also defines a single parameter and
|
514
531
|
# the name of the method ends with a = character.
|
515
|
-
writer_method_with_sig =
|
516
|
-
type == :req &&
|
517
|
-
|
532
|
+
writer_method_with_sig = (
|
533
|
+
signature && type == :req &&
|
534
|
+
parameters.size == 1 &&
|
518
535
|
signature.arg_types.size == 1 &&
|
519
536
|
method_name[-1] == "="
|
537
|
+
)
|
520
538
|
|
521
539
|
name = if writer_method_with_sig
|
522
|
-
method_name[0...-1].to_sym
|
540
|
+
T.must(method_name[0...-1]).to_sym
|
523
541
|
else
|
524
|
-
|
542
|
+
fallback_arg_name
|
525
543
|
end
|
526
544
|
end
|
527
545
|
|
528
546
|
# Sanitize param names
|
529
|
-
name = name.to_s.gsub(/[^a-zA-Z0-9_]/,
|
547
|
+
name = name.to_s.gsub(/[^a-zA-Z0-9_]/, fallback_arg_name)
|
530
548
|
|
549
|
+
[type, name]
|
550
|
+
end
|
551
|
+
|
552
|
+
parameter_list = sanitized_parameters.map do |type, name|
|
531
553
|
case type
|
532
554
|
when :req
|
533
555
|
name
|
@@ -546,25 +568,30 @@ module Tapioca
|
|
546
568
|
end
|
547
569
|
end.join(', ')
|
548
570
|
|
549
|
-
|
571
|
+
parameter_list = "(#{parameter_list})" if parameter_list != ""
|
572
|
+
signature_str = indented(compile_signature(signature, sanitized_parameters)) if signature
|
550
573
|
|
551
|
-
signature_str = indented(compile_signature(signature)) if signature
|
552
574
|
[
|
553
575
|
signature_str,
|
554
|
-
indented("def #{method_name}#{
|
576
|
+
indented("def #{method_name}#{parameter_list}; end"),
|
555
577
|
].compact.join("\n")
|
556
578
|
end
|
557
579
|
|
558
580
|
TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
|
559
581
|
|
560
|
-
sig { params(signature: T.untyped).returns(String) }
|
561
|
-
def compile_signature(signature)
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
582
|
+
sig { params(signature: T.untyped, parameters: T::Array[[Symbol, String]]).returns(String) }
|
583
|
+
def compile_signature(signature, parameters)
|
584
|
+
parameter_types = T.let(signature.arg_types.to_h, T::Hash[Symbol, T::Types::Base])
|
585
|
+
parameter_types.merge!(signature.kwarg_types)
|
586
|
+
parameter_types[signature.rest_name] = signature.rest_type if signature.has_rest
|
587
|
+
parameter_types[signature.keyrest_name] = signature.keyrest_type if signature.has_keyrest
|
588
|
+
parameter_types[signature.block_name] = signature.block_type if signature.block_name
|
589
|
+
|
590
|
+
params = parameters.map do |_, name|
|
591
|
+
type = parameter_types[name.to_sym]
|
592
|
+
"#{name}: #{type}"
|
593
|
+
end.join(", ")
|
566
594
|
|
567
|
-
params = params.compact.map { |name, type| "#{name}: #{type}" }.join(", ")
|
568
595
|
returns = type_of(signature.return_type)
|
569
596
|
|
570
597
|
type_parameters = (params + returns).scan(TYPE_PARAMETER_MATCHER).flatten.uniq.map { |p| ":#{p}" }.join(", ")
|
@@ -591,6 +618,7 @@ module Tapioca
|
|
591
618
|
signature_body = signature_body
|
592
619
|
.gsub(".returns(<VOID>)", ".void")
|
593
620
|
.gsub("<NOT-TYPED>", "T.untyped")
|
621
|
+
.gsub(".params()", "")
|
594
622
|
.gsub(TYPE_PARAMETER_MATCHER, "T.type_parameter(:\\1)")[1..-1]
|
595
623
|
|
596
624
|
"sig { #{signature_body} }"
|
@@ -1,7 +1,9 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
class Class
|
5
|
+
extend T::Sig
|
6
|
+
|
5
7
|
# Returns an array with all classes that are < than its receiver.
|
6
8
|
#
|
7
9
|
# class C; end
|
@@ -15,9 +17,12 @@ class Class
|
|
15
17
|
#
|
16
18
|
# class D < C; end
|
17
19
|
# C.descendants # => [B, A, D]
|
20
|
+
sig { returns(T::Array[Class]) }
|
18
21
|
def descendants
|
19
|
-
ObjectSpace.each_object(singleton_class).reject do |k|
|
20
|
-
k.singleton_class? || k == self
|
22
|
+
result = ObjectSpace.each_object(singleton_class).reject do |k|
|
23
|
+
T.cast(k, Module).singleton_class? || k == self
|
21
24
|
end
|
25
|
+
|
26
|
+
T.cast(result, T::Array[Class])
|
22
27
|
end
|
23
28
|
end
|
data/lib/tapioca/gemfile.rb
CHANGED
data/lib/tapioca/generator.rb
CHANGED
data/lib/tapioca/loader.rb
CHANGED
data/lib/tapioca/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapioca
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ufuk Kayserilioglu
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2020-
|
14
|
+
date: 2020-11-20 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: pry
|
@@ -155,7 +155,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
155
155
|
requirements:
|
156
156
|
- - ">="
|
157
157
|
- !ruby/object:Gem::Version
|
158
|
-
version: 2.
|
158
|
+
version: '2.4'
|
159
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
160
160
|
requirements:
|
161
161
|
- - ">="
|