sord 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +62 -25
- data/Rakefile +72 -0
- data/exe/sord +39 -4
- data/lib/sord/logging.rb +43 -0
- data/lib/sord/rbi_generator.rb +144 -40
- data/lib/sord/resolver.rb +82 -0
- data/lib/sord/type_converter.rb +55 -13
- data/lib/sord/version.rb +1 -1
- data/rbi/sord.rbi +165 -0
- data/sorbet/config +0 -2
- data/sorbet/rbi/gems/colorize.rbi +81 -0
- data/sorbet/rbi/gems/docile.rbi +31 -0
- data/sorbet/rbi/gems/rake.rbi +7 -0
- data/sorbet/rbi/gems/rspec-core.rbi +16 -0
- data/sorbet/rbi/gems/rspec-expectations.rbi +389 -0
- data/sorbet/rbi/gems/rspec-mocks.rbi +823 -0
- data/sorbet/rbi/gems/rspec-support.rbi +143 -1
- data/sorbet/rbi/gems/rspec.rbi +1 -1
- data/sorbet/rbi/gems/simplecov-html.rbi +30 -0
- data/sorbet/rbi/gems/simplecov.rbi +223 -0
- data/sorbet/rbi/gems/sorbet-runtime.rbi +647 -0
- data/sorbet/rbi/gems/yard.rbi +13 -13
- data/sorbet/rbi/hidden-definitions/errors.txt +1579 -2264
- data/sorbet/rbi/hidden-definitions/hidden.rbi +2918 -845
- data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +15 -5
- data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +1 -1
- data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +1 -1
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4302b93ba07124b24e91c6db6a2757d2fe148c573f3b86a918c72155e83d0d5
|
4
|
+
data.tar.gz: 610ce8222970fb4da8f5d01368a21b1aef4067936f6883b2eb5522e567e8fcce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a3798003e636dc6baba752bbae52718eefb3f43a5562d7fb3659e39d0a0245c39dd10d297433f05df12f60ac102b93a6ad1b71bc0e1fcb2bf3405233dfb834c
|
7
|
+
data.tar.gz: 0b9a87eea2db2c948006c89c0970175c98471f0e98358a190c17e6c8e064ff59c5849ea73b0081ef6cd09975488884e333b359a44f8d79a8dac78d6f7f461c3e
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
## Overview
|
4
4
|
|
5
|
-
Sord is a **So**rbet and YA**RD**
|
6
|
-
Sorbet type signatures files by
|
7
|
-
documentation comments.
|
5
|
+
Sord is a [**So**rbet](https://sorbet.org) and [YA**RD**](https://sorbet.org)
|
6
|
+
crossover. It can automatically generate Sorbet type signatures files by
|
7
|
+
looking at the types specified in YARD documentation comments.
|
8
8
|
|
9
9
|
If your project is already YARD documented, then this can generate most of the
|
10
10
|
Sorbet signatures you need!
|
@@ -16,14 +16,17 @@ Sord has the following features:
|
|
16
16
|
- Can infer setter parameter type from the corresponding getter's return type
|
17
17
|
- Recognises mixins (`include` and `extend`)
|
18
18
|
- Support for generic types such as `Array<T>` and `Hash<K, V>`
|
19
|
+
- Can infer namespaced classes (`[Bar]` can become `GemName::Foo::Bar`)
|
20
|
+
- Handles return types which can be `nil` (`T.nilable`)
|
21
|
+
- Handles duck types (`T.untyped`)
|
22
|
+
- Support for ordered list types (`[Array(Integer, Symbol)]` becomes `[Integer, Symbol]`)
|
23
|
+
- Support for boolean types (`[true, false]` becomes `T::Boolean`)
|
24
|
+
- Support for `&block` parameters documented with `@yieldparam` and `@yieldreturn`
|
19
25
|
|
20
26
|
## Usage
|
21
27
|
|
22
28
|
Install Sord with `gem install sord`.
|
23
29
|
|
24
|
-
**NOTE**: You need to run `yard` before you generate the `.rbi` file or
|
25
|
-
Sord won't have any information to work with.
|
26
|
-
|
27
30
|
Sord is a command line tool. To use it, open a terminal in the root directory
|
28
31
|
of your project and invoke `sord`, passing a path where you'd like to save your
|
29
32
|
`.rbi` (this file will be overwritten):
|
@@ -32,9 +35,18 @@ of your project and invoke `sord`, passing a path where you'd like to save your
|
|
32
35
|
sord defs.rbi
|
33
36
|
```
|
34
37
|
|
35
|
-
Sord will print information about what it's inferred
|
36
|
-
fix any issues in the YARD documentation, as any edits
|
37
|
-
RBI file will be replaced if you re-run Sord.
|
38
|
+
Sord will generate YARD docs and then print information about what it's inferred
|
39
|
+
as it runs. It is best to fix any issues in the YARD documentation, as any edits
|
40
|
+
made to the resulting RBI file will be replaced if you re-run Sord.
|
41
|
+
|
42
|
+
RBI files generated by Sord can be used in two main ways:
|
43
|
+
|
44
|
+
- [Shipped in the gem itself](https://sorbet.org/docs/rbi#rbis-within-gems).
|
45
|
+
- Contributed to [sorbet-typed](https://github.com/sorbet/sorbet-typed).
|
46
|
+
|
47
|
+
Generally, you should ship the type signatures with your gem if possible.
|
48
|
+
sorbet-typed is meant to be a place for gems that are no longer updated or
|
49
|
+
where the maintainer is unwilling to ship type signatures with the gem itself.
|
38
50
|
|
39
51
|
### Flags
|
40
52
|
|
@@ -42,6 +54,19 @@ Sord also takes some flags to alter the generated `.rbi` file:
|
|
42
54
|
|
43
55
|
- `--no-comments`: Generates the `.rbi` file without any comments about
|
44
56
|
warnings/inferences/errors.
|
57
|
+
- `--no-regenerate`: By default, Sord will regenerate a repository's YARD
|
58
|
+
docs for you. This option skips regenerating the YARD docs.
|
59
|
+
- `--break-params`: Determines how many parameters are necessary before
|
60
|
+
the signature is changed from a single-line to a multi-line block.
|
61
|
+
(Default: 4)
|
62
|
+
- `--replace-errors-with-untyped`: Uses `T.untyped` instead of `SORD_ERROR_*` constants.
|
63
|
+
- `--include-messages` and `--exclude-messages`: Used to filter the logging
|
64
|
+
messages given by Sord. `--include-messages` acts as a whitelist, printing
|
65
|
+
only messages of the specified logging kinds, whereas `--exclude-messages`
|
66
|
+
acts as a blacklist and suppresses the specified logging kinds. Both flags
|
67
|
+
take a comma-separated list of logging kinds, for example `omit,infer`.
|
68
|
+
When using `--include-messages`, the `done` kind is included by default.
|
69
|
+
(You cannot specify both `--include-messages` and `--exclude-messages`.)
|
45
70
|
|
46
71
|
## Example
|
47
72
|
|
@@ -89,22 +114,27 @@ The `test.rbi` file then contains a complete RBI file for `test.rb`:
|
|
89
114
|
```ruby
|
90
115
|
# typed: strong
|
91
116
|
module Example
|
92
|
-
class Person
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
117
|
+
class Person
|
118
|
+
sig { params(name: String, age: Integer).returns(Example::Person) }
|
119
|
+
def initialize(name, age); end
|
120
|
+
|
121
|
+
sig { returns(String) }
|
122
|
+
def name(); end
|
123
|
+
|
124
|
+
# sord infer - inferred type of parameter "value" as String using getter's return type
|
125
|
+
sig { params(value: String).returns(String) }
|
126
|
+
def name=(value); end
|
127
|
+
|
128
|
+
sig { returns(Integer) }
|
129
|
+
def age(); end
|
130
|
+
|
131
|
+
# sord infer - inferred type of parameter "value" as Integer using getter's return type
|
132
|
+
sig { params(value: Integer).returns(Integer) }
|
133
|
+
def age=(value); end
|
134
|
+
|
135
|
+
sig { params(possible_names: T::Array[String], possible_ages: T::Array[Integer]).returns(Example::Person) }
|
136
|
+
def self.construct_randomly(possible_names, possible_ages); end
|
137
|
+
end
|
108
138
|
end
|
109
139
|
```
|
110
140
|
|
@@ -124,6 +154,13 @@ The general rule of thumb for type conversions is:
|
|
124
154
|
|
125
155
|
Bug reports and pull requests are welcome on GitHub at https://github.com/AaronC81/sord. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
126
156
|
|
157
|
+
While contributing, if you want to see the results of your changes to Sord you
|
158
|
+
can use the `examples:seed` Rake task. The task uses Sord to generate RBIs for
|
159
|
+
a number of open source Ruby gems, including Bundler, Haml, Rouge, and RSpec.
|
160
|
+
`rake examples:seed` (and `rake examples:reseed` to regenerate the RBI files)
|
161
|
+
will clone the repositories of these gems into `sord_examples/` and then
|
162
|
+
generate the RBI files into the same directory.
|
163
|
+
|
127
164
|
## License
|
128
165
|
|
129
166
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -4,3 +4,75 @@ require "rspec/core/rake_task"
|
|
4
4
|
RSpec::Core::RakeTask.new(:spec)
|
5
5
|
|
6
6
|
task :default => :spec
|
7
|
+
|
8
|
+
REPOS = {
|
9
|
+
addressable: 'https://github.com/sporkmonger/addressable',
|
10
|
+
bundler: 'https://github.com/bundler/bundler',
|
11
|
+
discordrb: 'https://github.com/meew0/discordrb',
|
12
|
+
gitlab: 'https://github.com/NARKOZ/gitlab',
|
13
|
+
haml: 'https://github.com/haml/haml',
|
14
|
+
oga: 'https://gitlab.com/yorickpeterse/oga',
|
15
|
+
rouge: 'https://github.com/rouge-ruby/rouge',
|
16
|
+
'rspec-core': 'https://github.com/rspec/rspec-core',
|
17
|
+
yard: 'https://github.com/lsegal/yard',
|
18
|
+
zeitwerk: 'https://github.com/fxn/zeitwerk'
|
19
|
+
}
|
20
|
+
|
21
|
+
namespace :examples do
|
22
|
+
require 'fileutils'
|
23
|
+
require 'colorize'
|
24
|
+
|
25
|
+
desc "Clone git repositories and run Sord on them as examples"
|
26
|
+
task :seed do
|
27
|
+
if File.directory?('sord_examples')
|
28
|
+
puts 'sord_examples directory already exists, please delete the directory or run a reseed!'.red
|
29
|
+
exit
|
30
|
+
end
|
31
|
+
|
32
|
+
FileUtils.mkdir 'sord_examples'
|
33
|
+
FileUtils.cd 'sord_examples'
|
34
|
+
|
35
|
+
Bundler.with_clean_env do
|
36
|
+
# Shallow clone each of the repositories, then bundle install and run sord.
|
37
|
+
REPOS.each do |name, url|
|
38
|
+
puts "Cloning #{name}..."
|
39
|
+
system("git clone #{url} --depth=1")
|
40
|
+
FileUtils.cd name.to_s
|
41
|
+
# Add sord to gemfile.
|
42
|
+
`echo "gem 'sord', path: '../../'" >> Gemfile`
|
43
|
+
# Run bundle install.
|
44
|
+
system('bundle install')
|
45
|
+
# Generate sri
|
46
|
+
puts "Generating rbi for #{name}..."
|
47
|
+
system("bundle exec sord ../#{name}.rbi")
|
48
|
+
puts "#{name}.rbi generated!"
|
49
|
+
FileUtils.cd '..'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
puts "Seeding complete!".green
|
54
|
+
end
|
55
|
+
|
56
|
+
desc 'Regenerate the rbi files in sord_examples.'
|
57
|
+
task :reseed do
|
58
|
+
FileUtils.cd 'sord_examples'
|
59
|
+
|
60
|
+
REPOS.keys.each do |name|
|
61
|
+
FileUtils.cd name.to_s
|
62
|
+
puts "Regenerating rbi file for #{name}..."
|
63
|
+
Bundler.with_clean_env do
|
64
|
+
system("bundle exec sord ../#{name}.rbi --no-regenerate")
|
65
|
+
end
|
66
|
+
FileUtils.cd '..'
|
67
|
+
end
|
68
|
+
|
69
|
+
puts "Re-seeding complete!".green
|
70
|
+
end
|
71
|
+
|
72
|
+
desc 'Delete the sord_examples directory to allow the seeder to run again.'
|
73
|
+
task :reset do
|
74
|
+
FileUtils.rm_rf 'sord_examples' if File.directory?('sord_examples')
|
75
|
+
puts 'Reset complete.'.green
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
data/exe/sord
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'sord'
|
3
3
|
require 'commander/import'
|
4
|
+
require 'bundler'
|
4
5
|
|
5
6
|
program :name, 'sord'
|
6
7
|
program :version, Sord::VERSION
|
@@ -12,19 +13,53 @@ command :gen do |c|
|
|
12
13
|
c.description = 'Generates an RBI file from this directory\'s YARD docs'
|
13
14
|
c.option '--[no-]comments', 'Controls informational/warning comments in the RBI file'
|
14
15
|
c.option '--[no-]regenerate', 'Controls whether YARD is executed before Sord runs'
|
16
|
+
c.option '--break-params INTEGER', Integer, 'Break params onto their own lines if there are this many'
|
17
|
+
c.option '--replace-errors-with-untyped', 'Uses T.untyped rather than SORD_ERROR_ constants'
|
18
|
+
c.option '--exclude-messages STRING', String, 'Blacklists a comma-separated string of log message types'
|
19
|
+
c.option '--include-messages STRING', String, 'Whitelists a comma-separated string of log message types'
|
15
20
|
|
16
21
|
c.action do |args, options|
|
17
|
-
options.default
|
22
|
+
options.default(
|
23
|
+
comments: true,
|
24
|
+
regenerate: true,
|
25
|
+
break_params: 4,
|
26
|
+
replace_errors_with_untyped: false,
|
27
|
+
exclude_messages: nil,
|
28
|
+
include_messages: nil,
|
29
|
+
)
|
18
30
|
|
19
31
|
if args.length != 1
|
20
32
|
Sord::Logging.error('Must specify filename')
|
21
33
|
exit 1
|
22
34
|
end
|
23
35
|
|
36
|
+
if options.include_messages && options.exclude_messages
|
37
|
+
Sord::Logging.error('Please specify only one of --include-messages and --exclude-messages.')
|
38
|
+
exit 1
|
39
|
+
elsif options.include_messages
|
40
|
+
whitelist = options.include_messages.split(',').map { |x| x.downcase.to_sym }
|
41
|
+
unless Sord::Logging.valid_types?(whitelist)
|
42
|
+
Sord::Logging.error('Not all types on your --include-messages list are valid.')
|
43
|
+
Sord::Logging.error("Valid options are: #{Sord::Logging::AVAILABLE_TYPES.map(&:to_s).join(', ')}")
|
44
|
+
exit 1
|
45
|
+
end
|
46
|
+
Sord::Logging.enabled_types = whitelist | [:done]
|
47
|
+
elsif options.exclude_messages
|
48
|
+
blacklist = options.exclude_messages.split(',').map { |x| x.downcase.to_sym }
|
49
|
+
unless Sord::Logging.valid_types?(blacklist)
|
50
|
+
Sord::Logging.error('Not all types on your --include-messages list are valid.')
|
51
|
+
Sord::Logging.error("Valid options are: #{Sord::Logging::AVAILABLE_TYPES.map(&:to_s).join(', ')}")
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
Sord::Logging.enabled_types = Sord::Logging::AVAILABLE_TYPES - blacklist
|
55
|
+
end
|
56
|
+
|
24
57
|
if options.regenerate
|
25
58
|
begin
|
26
59
|
Sord::Logging.info('Running YARD...')
|
27
|
-
|
60
|
+
Bundler.with_clean_env do
|
61
|
+
system('bundle exec yard')
|
62
|
+
end
|
28
63
|
rescue Errno::ENOENT
|
29
64
|
Sord::Logging.error('The YARD tool could not be found on your PATH.')
|
30
65
|
Sord::Logging.error('You may need to run \'gem install yard\'.')
|
@@ -33,6 +68,6 @@ command :gen do |c|
|
|
33
68
|
end
|
34
69
|
end
|
35
70
|
|
36
|
-
Sord::RbiGenerator.new(options).run(args.first)
|
71
|
+
Sord::RbiGenerator.new(options.__hash__).run(args.first)
|
37
72
|
end
|
38
|
-
end
|
73
|
+
end
|
data/lib/sord/logging.rb
CHANGED
@@ -28,6 +28,36 @@ module Sord
|
|
28
28
|
@@silent = value
|
29
29
|
end
|
30
30
|
|
31
|
+
# An array of all available logging types.
|
32
|
+
AVAILABLE_TYPES = [:warn, :info, :duck, :error, :infer, :omit, :done].freeze
|
33
|
+
|
34
|
+
@@enabled_types = AVAILABLE_TYPES
|
35
|
+
|
36
|
+
# Sets the array of log messages types which should be processed. Any not on
|
37
|
+
# this list will be discarded. This should be a subset of AVAILABLE_TYPES.
|
38
|
+
# @param [Array<Symbol>] value
|
39
|
+
# @return [void]
|
40
|
+
def self.enabled_types=(value)
|
41
|
+
raise 'invalid types' unless valid_types?(value)
|
42
|
+
@@enabled_types = value
|
43
|
+
end
|
44
|
+
|
45
|
+
# Gets the array of log messages types which should be processed. Any not on
|
46
|
+
# this list will be discarded.
|
47
|
+
# @return [Array<Symbol>]
|
48
|
+
# @return [void]
|
49
|
+
def self.enabled_types
|
50
|
+
@@enabled_types
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns a boolean indicating whether a given array is a valid value for
|
54
|
+
# #enabled_types.
|
55
|
+
# @param [Array<Symbol>] value
|
56
|
+
# @return [void]
|
57
|
+
def self.valid_types?(value)
|
58
|
+
(value - AVAILABLE_TYPES).empty?
|
59
|
+
end
|
60
|
+
|
31
61
|
# A generic log message writer which is called by all other specific logging
|
32
62
|
# methods. This shouldn't be called outside of the Logging class itself.
|
33
63
|
# @param [Symbol] kind The kind of log message this is.
|
@@ -39,7 +69,10 @@ module Sord
|
|
39
69
|
# is associated with, if any. This is shown before the log message if it is
|
40
70
|
# specified.
|
41
71
|
# @param [Integer] indent_level The level at which to indent the code.
|
72
|
+
# @return [void]
|
42
73
|
def self.generic(kind, header, msg, item, indent_level = 0)
|
74
|
+
return unless enabled_types.include?(kind)
|
75
|
+
|
43
76
|
if item
|
44
77
|
puts "#{header} (#{item.path.bold}) #{msg}" unless silent?
|
45
78
|
else
|
@@ -56,6 +89,7 @@ module Sord
|
|
56
89
|
# is associated with, if any. This is shown before the log message if it is
|
57
90
|
# specified.
|
58
91
|
# @param [Integer] indent_level The level at which to indent the code.
|
92
|
+
# @return [void]
|
59
93
|
def self.warn(msg, item = nil, indent_level = 0)
|
60
94
|
generic(:warn, '[WARN ]'.yellow, msg, item, indent_level)
|
61
95
|
end
|
@@ -67,6 +101,7 @@ module Sord
|
|
67
101
|
# is associated with, if any. This is shown before the log message if it is
|
68
102
|
# specified.
|
69
103
|
# @param [Integer] indent_level The level at which to indent the code.
|
104
|
+
# @return [void]
|
70
105
|
def self.info(msg, item = nil, indent_level = 0)
|
71
106
|
generic(:info, '[INFO ]', msg, item, indent_level)
|
72
107
|
end
|
@@ -79,6 +114,7 @@ module Sord
|
|
79
114
|
# is associated with, if any. This is shown before the log message if it is
|
80
115
|
# specified.
|
81
116
|
# @param [Integer] indent_level The level at which to indent the code.
|
117
|
+
# @return [void]
|
82
118
|
def self.duck(msg, item = nil, indent_level = 0)
|
83
119
|
generic(:duck, '[DUCK ]'.cyan, msg, item, indent_level)
|
84
120
|
end
|
@@ -90,6 +126,7 @@ module Sord
|
|
90
126
|
# is associated with, if any. This is shown before the log message if it is
|
91
127
|
# specified.
|
92
128
|
# @param [Integer] indent_level The level at which to indent the code.
|
129
|
+
# @return [void]
|
93
130
|
def self.error(msg, item = nil, indent_level = 0)
|
94
131
|
generic(:error, '[ERROR]'.red, msg, item, indent_level)
|
95
132
|
end
|
@@ -102,6 +139,7 @@ module Sord
|
|
102
139
|
# is associated with, if any. This is shown before the log message if it is
|
103
140
|
# specified.
|
104
141
|
# @param [Integer] indent_level The level at which to indent the code.
|
142
|
+
# @return [void]
|
105
143
|
def self.infer(msg, item = nil, indent_level = 0)
|
106
144
|
generic(:infer, '[INFER]'.light_blue, msg, item, indent_level)
|
107
145
|
end
|
@@ -114,16 +152,19 @@ module Sord
|
|
114
152
|
# is associated with, if any. This is shown before the log message if it is
|
115
153
|
# specified.
|
116
154
|
# @param [Integer] indent_level The level at which to indent the code.
|
155
|
+
# @return [void]
|
117
156
|
def self.omit(msg, item = nil, indent_level = 0)
|
118
157
|
generic(:omit, '[OMIT ]'.magenta, msg, item, indent_level)
|
119
158
|
end
|
120
159
|
|
121
160
|
# Print a done message. This should be used when a process completes
|
122
161
|
# successfully.
|
162
|
+
# @param [String] msg The log message to write.
|
123
163
|
# @param [YARD::CodeObjects::Base] item The CodeObject which this log
|
124
164
|
# is associated with, if any. This is shown before the log message if it is
|
125
165
|
# specified.
|
126
166
|
# @param [Integer] indent_level The level at which to indent the code.
|
167
|
+
# @return [void]
|
127
168
|
def self.done(msg, item = nil, indent_level = 0)
|
128
169
|
generic(:done, '[DONE ]'.green, msg, item)
|
129
170
|
end
|
@@ -135,6 +176,7 @@ module Sord
|
|
135
176
|
# is associated with, if any. This is shown before the log message if it is
|
136
177
|
# specified.
|
137
178
|
# @param [Integer] indent_level The level at which to indent the code.
|
179
|
+
# @return [void]
|
138
180
|
def self.invoke_hooks(kind, msg, item, indent_level = 0)
|
139
181
|
@@hooks.each do |hook|
|
140
182
|
hook.(kind, msg, item, indent_level) rescue nil
|
@@ -149,6 +191,7 @@ module Sord
|
|
149
191
|
# specified.
|
150
192
|
# @yieldparam [Integer] indent_level The level at which to indent the code.
|
151
193
|
# @yieldreturn [void]
|
194
|
+
# @return [void]
|
152
195
|
def self.add_hook(&blk)
|
153
196
|
@@hooks << blk
|
154
197
|
end
|
data/lib/sord/rbi_generator.rb
CHANGED
@@ -21,19 +21,30 @@ module Sord
|
|
21
21
|
# [message, item, line].
|
22
22
|
attr_reader :warnings
|
23
23
|
|
24
|
+
# @return [Boolean] A boolean indicating whether the next item is the first
|
25
|
+
# in its namespace. This is used to determine whether to insert a blank
|
26
|
+
# line before it or not.
|
27
|
+
attr_accessor :next_item_is_first_in_namespace
|
28
|
+
|
24
29
|
# Create a new RBI generator.
|
25
30
|
# @param [Hash] options
|
26
|
-
# @
|
31
|
+
# @option options [Integer] break_params
|
32
|
+
# @option options [Boolean] replace_errors_with_untyped
|
33
|
+
# @option options [Boolean] comments
|
34
|
+
# @return [void]
|
27
35
|
def initialize(options)
|
28
36
|
@rbi_contents = ['# typed: strong']
|
29
37
|
@namespace_count = 0
|
30
38
|
@method_count = 0
|
39
|
+
@break_params = options[:break_params]
|
40
|
+
@replace_errors_with_untyped = options[:replace_errors_with_untyped]
|
31
41
|
@warnings = []
|
42
|
+
@next_item_is_first_in_namespace = true
|
32
43
|
|
33
44
|
# Hook the logger so that messages are added as comments to the RBI file
|
34
45
|
Logging.add_hook do |type, msg, item, indent_level = 0|
|
35
46
|
rbi_contents << "#{' ' * (indent_level + 1)}# sord #{type} - #{msg}"
|
36
|
-
end if options
|
47
|
+
end if options[:comments]
|
37
48
|
|
38
49
|
# Hook the logger so that warnings are collected
|
39
50
|
Logging.add_hook do |type, msg, item, indent_level = 0|
|
@@ -54,21 +65,57 @@ module Sord
|
|
54
65
|
@method_count += 1
|
55
66
|
end
|
56
67
|
|
68
|
+
# Adds a single blank line to the RBI file, unless this item is the first
|
69
|
+
# in its namespace.
|
70
|
+
# @return [void]
|
71
|
+
def add_blank
|
72
|
+
rbi_contents << '' unless next_item_is_first_in_namespace
|
73
|
+
self.next_item_is_first_in_namespace = false
|
74
|
+
end
|
75
|
+
|
57
76
|
# Given a YARD CodeObject, add lines defining its mixins (that is, extends
|
58
|
-
# and includes) to the current RBI file.
|
77
|
+
# and includes) to the current RBI file. Returns the number of mixins.
|
59
78
|
# @param [YARD::CodeObjects::Base] item
|
60
79
|
# @param [Integer] indent_level
|
61
|
-
# @return [
|
80
|
+
# @return [Integer]
|
62
81
|
def add_mixins(item, indent_level)
|
63
|
-
|
64
|
-
|
82
|
+
includes = item.instance_mixins
|
83
|
+
extends = item.class_mixins
|
65
84
|
|
66
|
-
extends.
|
85
|
+
extends.reverse_each do |this_extend|
|
67
86
|
rbi_contents << "#{' ' * (indent_level + 1)}extend #{this_extend.path}"
|
68
87
|
end
|
69
|
-
includes.
|
88
|
+
includes.reverse_each do |this_include|
|
70
89
|
rbi_contents << "#{' ' * (indent_level + 1)}include #{this_include.path}"
|
71
90
|
end
|
91
|
+
|
92
|
+
extends.length + includes.length
|
93
|
+
end
|
94
|
+
|
95
|
+
# Given an array of parameters and a return type, inserts the signature for
|
96
|
+
# a method with those properties into the current RBI file.
|
97
|
+
# @param [Array<String>] params
|
98
|
+
# @param [String] returns
|
99
|
+
# @param [Integer] indent_level
|
100
|
+
# @return [void]
|
101
|
+
def add_signature(params, returns, indent_level)
|
102
|
+
if params.empty?
|
103
|
+
rbi_contents << "#{' ' * (indent_level + 1)}sig { #{returns} }"
|
104
|
+
return
|
105
|
+
end
|
106
|
+
|
107
|
+
if params.length >= @break_params
|
108
|
+
rbi_contents << "#{' ' * (indent_level + 1)}sig do"
|
109
|
+
rbi_contents << "#{' ' * (indent_level + 2)}params("
|
110
|
+
params.each.with_index do |param, i|
|
111
|
+
terminator = params.length - 1 == i ? '' : ','
|
112
|
+
rbi_contents << "#{' ' * (indent_level + 3)}#{param}#{terminator}"
|
113
|
+
end
|
114
|
+
rbi_contents << "#{' ' * (indent_level + 2)}).#{returns}"
|
115
|
+
rbi_contents << "#{' ' * (indent_level + 1)}end"
|
116
|
+
else
|
117
|
+
rbi_contents << "#{' ' * (indent_level + 1)}sig { params(#{params.join(', ')}).#{returns} }"
|
118
|
+
end
|
72
119
|
end
|
73
120
|
|
74
121
|
# Given a YARD NamespaceObject, add lines defining its methods and their
|
@@ -77,8 +124,6 @@ module Sord
|
|
77
124
|
# @param [Integer] indent_level
|
78
125
|
# @return [void]
|
79
126
|
def add_methods(item, indent_level)
|
80
|
-
# TODO: block documentation
|
81
|
-
|
82
127
|
item.meths.each do |meth|
|
83
128
|
count_method
|
84
129
|
|
@@ -88,6 +133,8 @@ module Sord
|
|
88
133
|
next
|
89
134
|
end
|
90
135
|
|
136
|
+
add_blank
|
137
|
+
|
91
138
|
parameter_list = meth.parameters.map do |name, default|
|
92
139
|
# Handle these three main cases:
|
93
140
|
# - def method(param) or def method(param:)
|
@@ -107,55 +154,94 @@ module Sord
|
|
107
154
|
# (The gsubs allow for better splat-argument compatibility)
|
108
155
|
parameter_names_to_tags = meth.parameters.map do |name, _|
|
109
156
|
[name, meth.tags('param')
|
110
|
-
.find { |p| p.name
|
157
|
+
.find { |p| p.name&.gsub('*', '') == name.gsub('*', '') }]
|
111
158
|
end.to_h
|
112
159
|
|
113
160
|
sig_params_list = parameter_names_to_tags.map do |name, tag|
|
114
161
|
name = name.gsub('*', '')
|
115
162
|
|
116
163
|
if tag
|
117
|
-
"#{name}: #{TypeConverter.yard_to_sorbet(tag.types, meth)}"
|
164
|
+
"#{name}: #{TypeConverter.yard_to_sorbet(tag.types, meth, indent_level, @replace_errors_with_untyped)}"
|
118
165
|
elsif name.start_with? '&'
|
119
|
-
# Cut the ampersand from the block parameter name
|
120
|
-
|
166
|
+
# Cut the ampersand from the block parameter name
|
167
|
+
name = name.gsub('&', '')
|
168
|
+
|
169
|
+
# Find yieldparams and yieldreturn
|
170
|
+
yieldparams = meth.tags('yieldparam')
|
171
|
+
yieldreturn = meth.tag('yieldreturn')&.types
|
172
|
+
yieldreturn = nil if yieldreturn&.length == 1 &&
|
173
|
+
yieldreturn&.first&.downcase == 'void'
|
174
|
+
|
175
|
+
# Create strings
|
176
|
+
params_string = yieldparams.map do |param|
|
177
|
+
"#{param.name.gsub('*', '')}: #{TypeConverter.yard_to_sorbet(param.types, meth, indent_level, @replace_errors_with_untyped)}" unless param.name.nil?
|
178
|
+
end.join(', ')
|
179
|
+
return_string = TypeConverter.yard_to_sorbet(yieldreturn, meth, indent_level, @replace_errors_with_untyped)
|
180
|
+
|
181
|
+
# Create proc types, if possible
|
182
|
+
if yieldparams.empty? && yieldreturn.nil?
|
183
|
+
"#{name}: T.untyped"
|
184
|
+
elsif yieldreturn.nil?
|
185
|
+
"#{name}: T.proc#{params_string.empty? ? '' : ".params(#{params_string})"}.void"
|
186
|
+
else
|
187
|
+
"#{name}: T.proc#{params_string.empty? ? '' : ".params(#{params_string})"}.returns(#{return_string})"
|
188
|
+
end
|
121
189
|
elsif meth.path.end_with? '='
|
122
190
|
# Look for the matching getter method
|
123
191
|
getter_path = meth.path[0...-1]
|
124
192
|
getter = item.meths.find { |m| m.path == getter_path }
|
125
193
|
|
126
194
|
unless getter
|
127
|
-
|
128
|
-
|
195
|
+
if parameter_names_to_tags.length == 1 \
|
196
|
+
&& meth.tags('param').length == 1 \
|
197
|
+
&& meth.tag('param').types
|
198
|
+
|
199
|
+
Logging.infer("argument name in single @param inferred as #{parameter_names_to_tags.first.first.inspect}", meth, indent_level)
|
200
|
+
next "#{name}: #{TypeConverter.yard_to_sorbet(meth.tag('param').types, meth, indent_level, @replace_errors_with_untyped)}"
|
201
|
+
else
|
202
|
+
Logging.omit("no YARD type given for #{name.inspect}, using T.untyped", meth, indent_level)
|
203
|
+
next "#{name}: T.untyped"
|
204
|
+
end
|
129
205
|
end
|
130
206
|
|
131
207
|
inferred_type = TypeConverter.yard_to_sorbet(
|
132
|
-
getter.tags('return').flat_map(&:types), meth)
|
208
|
+
getter.tags('return').flat_map(&:types), meth, indent_level, @replace_errors_with_untyped)
|
133
209
|
|
134
210
|
Logging.infer("inferred type of parameter #{name.inspect} as #{inferred_type} using getter's return type", meth, indent_level)
|
135
211
|
# Get rid of : on keyword arguments.
|
136
212
|
name = name.chop if name.end_with?(':')
|
137
213
|
"#{name}: #{inferred_type}"
|
138
214
|
else
|
139
|
-
|
140
|
-
#
|
141
|
-
|
142
|
-
|
215
|
+
# Is this the only argument, and was a @param specified without an
|
216
|
+
# argument name? If so, infer it
|
217
|
+
if parameter_names_to_tags.length == 1 \
|
218
|
+
&& meth.tags('param').length == 1 \
|
219
|
+
&& meth.tag('param').types
|
220
|
+
|
221
|
+
Logging.infer("argument name in single @param inferred as #{parameter_names_to_tags.first.first.inspect}", meth, indent_level)
|
222
|
+
"#{name}: #{TypeConverter.yard_to_sorbet(meth.tag('param').types, meth, indent_level, @replace_errors_with_untyped)}"
|
223
|
+
else
|
224
|
+
Logging.omit("no YARD type given for #{name.inspect}, using T.untyped", meth, indent_level)
|
225
|
+
# Get rid of : on keyword arguments.
|
226
|
+
name = name.chop if name.end_with?(':')
|
227
|
+
"#{name}: T.untyped"
|
228
|
+
end
|
143
229
|
end
|
144
|
-
end
|
230
|
+
end
|
145
231
|
|
146
232
|
return_tags = meth.tags('return')
|
147
233
|
returns = if return_tags.length == 0
|
148
|
-
"
|
234
|
+
Logging.omit("no YARD return type given, using T.untyped", meth, indent_level)
|
235
|
+
"returns(T.untyped)"
|
149
236
|
elsif return_tags.length == 1 && return_tags&.first&.types&.first&.downcase == "void"
|
150
237
|
"void"
|
151
238
|
else
|
152
|
-
"returns(#{TypeConverter.yard_to_sorbet(meth.tag('return').types, meth)})"
|
239
|
+
"returns(#{TypeConverter.yard_to_sorbet(meth.tag('return').types, meth, indent_level, @replace_errors_with_untyped)})"
|
153
240
|
end
|
154
241
|
|
155
242
|
prefix = meth.scope == :class ? 'self.' : ''
|
156
243
|
|
157
|
-
|
158
|
-
rbi_contents << sig
|
244
|
+
add_signature(sig_params_list, returns, indent_level)
|
159
245
|
|
160
246
|
rbi_contents << "#{' ' * (indent_level + 1)}def #{prefix}#{meth.name}(#{parameter_list}); end"
|
161
247
|
end
|
@@ -165,41 +251,55 @@ module Sord
|
|
165
251
|
# and children to the RBI file.
|
166
252
|
# @param [YARD::CodeObjects::NamespaceObject] item
|
167
253
|
# @param [Integer] indent_level
|
254
|
+
# @return [void]
|
168
255
|
def add_namespace(item, indent_level = 0)
|
169
256
|
count_namespace
|
257
|
+
add_blank
|
170
258
|
|
171
259
|
if item.type == :class && item.superclass.to_s != "Object"
|
172
260
|
rbi_contents << "#{' ' * indent_level}class #{item.name} < #{item.superclass.path}"
|
173
261
|
else
|
174
262
|
rbi_contents << "#{' ' * indent_level}#{item.type} #{item.name}"
|
175
263
|
end
|
176
|
-
|
264
|
+
|
265
|
+
self.next_item_is_first_in_namespace = true
|
266
|
+
if add_mixins(item, indent_level) > 0
|
267
|
+
self.next_item_is_first_in_namespace = false
|
268
|
+
end
|
177
269
|
add_methods(item, indent_level)
|
178
270
|
|
179
271
|
item.children.select { |x| [:class, :module].include?(x.type) }
|
180
272
|
.each { |child| add_namespace(child, indent_level + 1) }
|
181
273
|
|
274
|
+
self.next_item_is_first_in_namespace = false
|
275
|
+
|
182
276
|
rbi_contents << "#{' ' * indent_level}end"
|
183
277
|
end
|
184
278
|
|
185
|
-
# Generates the RBI file
|
186
|
-
#
|
279
|
+
# Generates the RBI file from the loading registry and returns its contents.
|
280
|
+
# You must load a registry first!
|
281
|
+
# @return [String]
|
282
|
+
def generate
|
283
|
+
# Generate top-level modules, which recurses to all modules
|
284
|
+
YARD::Registry.root.children
|
285
|
+
.select { |x| [:class, :module].include?(x.type) }
|
286
|
+
.each { |child| add_namespace(child) }
|
287
|
+
|
288
|
+
rbi_contents.join("\n")
|
289
|
+
end
|
290
|
+
|
291
|
+
# Generates the RBI file and writes it to the given file path, printing a
|
292
|
+
# summary and any warnings at the end. The registry is also loaded.
|
293
|
+
# @param [String, nil] filename
|
187
294
|
# @return [void]
|
188
295
|
def run(filename)
|
189
|
-
raise
|
296
|
+
raise 'No filename specified' unless filename
|
190
297
|
|
191
298
|
# Get YARD ready
|
192
299
|
YARD::Registry.load!
|
193
300
|
|
194
|
-
# TODO: constants?
|
195
|
-
|
196
|
-
# Generate top-level modules, which recurses to all modules
|
197
|
-
YARD::Registry.root.children
|
198
|
-
.select { |x| [:class, :module].include?(x.type) }
|
199
|
-
.each { |child| add_namespace(child) }
|
200
|
-
|
201
301
|
# Write the file
|
202
|
-
File.write(filename,
|
302
|
+
File.write(filename, generate)
|
203
303
|
|
204
304
|
if object_count.zero?
|
205
305
|
Logging.warn("No objects processed.")
|
@@ -213,11 +313,15 @@ module Sord
|
|
213
313
|
|
214
314
|
unless warnings.empty?
|
215
315
|
Logging.warn("There were #{warnings.length} important warnings in the RBI file, listed below.")
|
216
|
-
|
316
|
+
if @replace_errors_with_untyped
|
317
|
+
Logging.warn("The types which caused them have been replaced with T.untyped.")
|
318
|
+
else
|
319
|
+
Logging.warn("The types which caused them have been replaced with SORD_ERROR_ constants.")
|
320
|
+
end
|
217
321
|
Logging.warn("Please edit the file near the line numbers given to fix these errors.")
|
218
322
|
Logging.warn("Alternatively, edit your YARD documentation so that your types are valid and re-run Sord.")
|
219
323
|
warnings.each do |(msg, item, line)|
|
220
|
-
puts " #{"Line #{line} |".light_black} (#{item
|
324
|
+
puts " #{"Line #{line} |".light_black} (#{item&.path&.bold}) #{msg}"
|
221
325
|
end
|
222
326
|
end
|
223
327
|
rescue
|