Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/soutaro/steep
Static type checker for Ruby
https://github.com/soutaro/steep
ruby typechecker
Last synced: 8 days ago
JSON representation
Static type checker for Ruby
- Host: GitHub
- URL: https://github.com/soutaro/steep
- Owner: soutaro
- License: mit
- Created: 2017-03-27T15:34:46.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2024-05-01T20:09:52.000Z (6 months ago)
- Last Synced: 2024-05-01T23:28:28.779Z (6 months ago)
- Topics: ruby, typechecker
- Language: Ruby
- Homepage:
- Size: 4.59 MB
- Stars: 1,325
- Watchers: 24
- Forks: 83
- Open Issues: 120
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Steep - Gradual Typing for Ruby
## Installation
Install via RubyGems.
$ gem install steep
### Requirements
Steep requires Ruby 2.6 or later.
## Usage
Steep does not infer types from Ruby programs, but requires declaring types and writing annotations.
You have to go on the following three steps.### 0. `steep init`
Run `steep init` to generate a configuration file.
```
$ steep init # Generates Steepfile
```Edit the `Steepfile`:
```rb
target :app do
check "lib"
signature "sig"library "pathname"
end
```### 1. Declare Types
Declare types in `.rbs` files in `sig` directory.
```
class Person
@name: String
@contacts: Array[Email | Phone]def initialize: (name: String) -> untyped
def name: -> String
def contacts: -> Array[Email | Phone]
def guess_country: -> (String | nil)
endclass Email
@address: Stringdef initialize: (address: String) -> untyped
def address: -> String
endclass Phone
@country: String
@number: Stringdef initialize: (country: String, number: String) -> untyped
def country: -> String
def number: -> Stringdef self.countries: -> Hash[String, String]
end
```* You can use simple *generics*, like `Hash[String, String]`.
* You can use *union types*, like `Email | Phone`.
* You have to declare not only public methods but also private methods and instance variables.
* You can declare *singleton methods*, like `self.countries`.
* There is `nil` type to represent *nullable* types.### 2. Write Ruby Code
Write Ruby code with annotations.
```rb
class Person
# `@dynamic` annotation is to tell steep that
# the `name` and `contacts` methods are defined without def syntax.
# (Steep can skip checking if the methods are implemented.)# @dynamic name, contacts
attr_reader :name
attr_reader :contactsdef initialize(name:)
@name = name
@contacts = []
enddef guess_country()
contacts.map do |contact|
# With case expression, simple type-case is implemented.
# `contact` has type of `Phone | Email` but in the `when` clause, contact has type of `Phone`.
case contact
when Phone
contact.country
end
end.compact.first
end
endclass Email
# @dynamic address
attr_reader :addressdef initialize(address:)
@address = address
enddef ==(other)
# `other` has type of `untyped`, which means type checking is skipped.
# No type errors can be detected in this method.
other.is_a?(self.class) && other.address == address
enddef hash
self.class.hash ^ address.hash
end
endclass Phone
# @dynamic country, number
attr_reader :country, :numberdef initialize(country:, number:)
@country = country
@number = number
enddef ==(other)
# You cannot use `case` for type case because `other` has type of `untyped`, not a union type.
# You have to explicitly declare the type of `other` in `if` expression.if other.is_a?(Phone)
# @type var other: Phone
other.country == country && other.number == number
end
enddef hash
self.class.hash ^ country.hash ^ number.hash
end
end
```### 3. Type Check
Run `steep check` command to type check. 💡
```
$ steep check
lib/phone.rb:46:0: MethodDefinitionMissing: module=::Phone, method=self.countries (class Phone)
```You now find `Phone.countries` method is not implemented yet. 🙃
## Prototyping signature
You can use `rbs prototype` command to generate a signature declaration.
```
$ rbs prototype rb lib/person.rb lib/email.rb lib/phone.rb
class Person
@name: untyped
@contacts: Array[untyped]
def initialize: (name: untyped) -> Array[untyped]
def guess_country: () -> untyped
endclass Email
@address: untyped
def initialize: (address: untyped) -> untyped
def ==: (untyped) -> untyped
def hash: () -> untyped
endclass Phone
@country: untyped
@number: untyped
def initialize: (country: untyped, number: untyped) -> untyped
def ==: (untyped) -> void
def hash: () -> untyped
end
```It prints all methods, classes, instance variables, and constants.
It can be a good starting point to writing signatures.Because it just prints all `def`s, you may find some odd points:
* The type of `initialize` in `Person` looks strange.
* There are no `attr_reader` methods extracted.Generally, these are by our design.
`rbs prototype` offers options: `rbi` to generate prototype from Sorbet RBI and `runtime` to generate from runtime API.
## Guides
There are some guides in the `guide` directory. I know we need more comprehensive set of documentations. Just started writing docs.
* [Guides](guides)
## Examples
You can find examples in `smoke` directory.
## IDEs
Steep implements some of the Language Server Protocol features.
- For **VSCode** please install [the plugin](https://github.com/soutaro/steep-vscode).
- For **SublimeText** please install [LSP](https://github.com/sublimelsp/LSP) package and follow [instructions](https://lsp.sublimetext.io/language_servers/#steep).
- For **Vim** or **Neovim** please install [ALE](https://github.com/dense-analysis/ale?tab=readme-ov-file#asynchronous-lint-engine). You may want to `let g:ale_ruby_steep_executable = 'bundle'` to use your bundled `steep` version.Other LSP supporting tools may work with Steep where it starts the server as `steep langserver`.
## Rake Tasks
Steep comes with a set of configurable Rake tasks.
```ruby
# Rakefilerequire "steep/rake_task"
Steep::RakeTask.new do |t|
t.check.severity_level = :error
t.watch.verbose
endtask default: [:steep]
```Use `bundle exec rake -T` to see all available tasks.
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/soutaro/steep.