Sord
Overview
Sord is a Sorbet and YA*RD* crossover. It can automatically generate Sorbet type signatures files by looking at the types specified in YARD documentation comments.
If your project is already YARD documented, then this can generate most of the Sorbet signatures you need!
Sord has the following features:
- Automatically generates signatures for modules, classes and methods
- Support for multiple parameter or return types (
T.any
) - Gracefully handles missing YARD types (
T.untyped
) - Can infer setter parameter type from the corresponding getter's return type
- Recognises mixins (
include
andextend
) - Support for generic types such as
Array<T>
andHash<K, V>
- Can infer namespaced classes (
[Bar]
can becomeGemName::Foo::Bar
) - Handles return types which can be
nil
(T.nilable
) - Handles duck types (
T.untyped
) - Support for ordered list types (
[Array(Integer, Symbol)]
becomes[Integer, Symbol]
) - Support for boolean types (
[true, false]
becomesT::Boolean
) - Support for
&block
parameters documented with@yieldparam
and@yieldreturn
Usage
Install Sord with gem install sord
.
Sord is a command line tool. To use it, open a terminal in the root directory
of your project and invoke sord
, passing a path where you'd like to save your
.rbi
(this file will be overwritten):
sord defs.rbi
Sord will generate YARD docs and then print information about what it's inferred as it runs. It is best to fix any issues in the YARD documentation, as any edits made to the resulting RBI file will be replaced if you re-run Sord.
RBI files generated by Sord can be used in two main ways:
- Shipped in the gem itself.
- Contributed to sorbet-typed.
Generally, you should ship the type signatures with your gem if possible. sorbet-typed is meant to be a place for gems that are no longer updated or where the maintainer is unwilling to ship type signatures with the gem itself.
Flags
Sord also takes some flags to alter the generated .rbi
file:
--no-comments
: Generates the.rbi
file without any comments about warnings/inferences/errors.--no-regenerate
: By default, Sord will regenerate a repository's YARD docs for you. This option skips regenerating the YARD docs.--break-params
: Determines how many parameters are necessary before the signature is changed from a single-line to a multi-line block. (Default: 4)--replace-errors-with-untyped
: UsesT.untyped
instead ofSORD_ERROR_*
constants.--include-messages
and--exclude-messages
: Used to filter the logging messages given by Sord.--include-messages
acts as a whitelist, printing only messages of the specified logging kinds, whereas--exclude-messages
acts as a blacklist and suppresses the specified logging kinds. Both flags take a comma-separated list of logging kinds, for exampleomit,infer
. When using--include-messages
, thedone
kind is included by default. (You cannot specify both--include-messages
and--exclude-messages
.)
Example
Say we have this file, called test.rb
:
module Example
class Person
# @param [String] name
# @param [Integer] age
# @return [Example::Person]
def initialize(name, age)
@name = name
@age = age
end
# @return [String] name
attr_accessor :name
# @return [Integer] age
attr_accessor :age
# @param [Array<String>] possible_names
# @param [Array<Integer>] possible_ages
# @return [Example::Person]
def self.construct_randomly(possible_names, possible_ages)
Person.new(possible_names.sample, possible_ages.sample)
end
end
end
First, generate a YARD registry by running yardoc test.rb
. Then, we can run
sord test.rbi
to generate the RBI file. (Careful not to overwrite your code
files! Note the .rbi
file extension.) In doing this, Sord prints:
[INFER] (Example::Person#name=) inferred type of parameter "value" as String using getter's return type
[INFER] (Example::Person#age=) inferred type of parameter "value" as Integer using getter's return type
[DONE ] Processed 8 objects
The test.rbi
file then contains a complete RBI file for test.rb
:
# typed: strong
module Example
class Person
sig { params(name: String, age: Integer).returns(Example::Person) }
def initialize(name, age); end
sig { returns(String) }
def name(); end
# sord infer - inferred type of parameter "value" as String using getter's return type
sig { params(value: String).returns(String) }
def name=(value); end
sig { returns(Integer) }
def age(); end
# sord infer - inferred type of parameter "value" as Integer using getter's return type
sig { params(value: Integer).returns(Integer) }
def age=(value); end
sig { params(possible_names: T::Array[String], possible_ages: T::Array[Integer]).returns(Example::Person) }
def self.construct_randomly(possible_names, possible_ages); end
end
end
Things to be aware of
The general rule of thumb for type conversions is:
- If Sord understands the YARD type, then it is converted into the Sorbet type.
- If the YARD type is missing, Sord fills in
T.untyped
. - If the YARD type can't be understood, Sord creates an undefined Ruby constant
with a similar name to the unknown YARD type. For example, the obviously
invalid YARD type
A%B
will become a constant calledSORD_ERROR_AB
. You should search through your resulting RBI to find and fix andSORD_ERROR
s.
Contributing
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 code of conduct.
While contributing, if you want to see the results of your changes to Sord you
can use the examples:seed
Rake task. The task uses Sord to generate RBIs for
a number of open source Ruby gems, including Bundler, Haml, Rouge, and RSpec.
rake examples:seed
(and rake examples:reseed
to regenerate the RBI files)
will clone the repositories of these gems into sord_examples/
and then
generate the RBI files into the same directory.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Sord project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.