• Stars
    star
    733
  • Rank 61,835 (Top 2 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 5 years ago
  • Updated about 1 month ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

The swiss army knife of RBI generation

⚠️ Note: This software is currently under active development. The API and interface should be considered unstable until a v1.0.0 release.

Tapioca logo

Tapioca - The swiss army knife of RBI generation

Build Status

Tapioca makes it easy to work with Sorbet in your codebase. It surfaces types and methods from many sources that Sorbet cannot otherwise see – such as gems, Rails and other DSLs – compiles them into RBI files and makes it easy for you to add gradual typing to your application.

Features:

  • Easy installation and configuration
  • Generation of RBI files for the gems used in your application
    • Automatic generation from your application's Gemfile
    • Importing of signatures from the source code of gems
    • Importing of documentation from the source code of gems
    • Synchronization validation for your CI
  • Generation of RBI files for various DSL patterns that relies on meta-programming
    • Automatic generation from your application's content
    • Support many DSL patterns such as Rails, Google Protobuf, SmartProperties and more out of the box
    • Extensible interface that allows you to write your own DSL compilers for other DSL patterns
    • Automatic generation of signatures for methods from known DSLs
    • Synchronization validation for your CI
  • Management of shim RBI files
    • Find useless definitions in shim RBI files from gems generated RBI files
    • Find useless definitions in shim RBI files from DSL generated RBI files
    • Find useless definitions in shim RBI files from Sorbet's embedded RBI for core and stdlib
    • Synchronization validation for your CI

Table of Contents

Installation

Add this line to your application's Gemfile:

group :development do
  gem 'tapioca', require: false
end

Run bundle install and make sure Tapioca is properly installed:

$ tapioca help

Commands:
  tapioca --version, -v      # show version
  tapioca annotations        # Pull gem RBI annotations from remote sources
  tapioca check-shims        # check duplicated definitions in shim RBIs
  tapioca configure          # initialize folder structure and type checking configuration
  tapioca dsl [constant...]  # generate RBIs for dynamic methods
  tapioca gem [gem...]       # generate RBIs from gems
  tapioca help [COMMAND]     # Describe available commands or one specific command
  tapioca init               # get project ready for type checking
  tapioca require            # generate the list of files to be required by tapioca
  tapioca todo               # generate the list of unresolved constants

Options:
  -c, [--config=<config file path>]  # Path to the Tapioca configuration file
                                     # Default: sorbet/tapioca/config.yml
  -V, [--verbose], [--no-verbose]    # Verbose output for debugging purposes

Getting started

Execute this command to get started:

$ bundle exec tapioca init

This will:

  1. create the configuration file for Sorbet, the configuration file for Tapioca and the require.rb file
  2. install the binstub for Tapioca in your app's bin/ folder, so that you can use bin/tapioca to run commands in your app
  3. pull the community RBI annotations from the central repository matching your app's gems
  4. generate the RBIs for your app's gems
  5. generate the RBI file for missing constants

See the following sections for more details about each step.

$ tapioca help init

Usage:
  tapioca init

Options:
  -c, [--config=<config file path>]  # Path to the Tapioca configuration file
                                     # Default: sorbet/tapioca/config.yml
  -V, [--verbose], [--no-verbose]    # Verbose output for debugging purposes

get project ready for type checking

Usage

Generating RBI files for gems

Sorbet does not read the code in your gem dependencies, so it does not know the constants and methods declared inside gems. Tapioca is able to load your gem dependencies from your application's Gemfile and compile RBI files to represent their content.

In order to generate the RBI files for the gems used in your application, run the following command:

$ bin/tapioca gems [gems...]

Removing RBI files of gems that have been removed:

  Nothing to do.

Generating RBI files of gems that are added or updated:

  Requiring all gems to prepare for compiling...    Done

  Compiled ansi
      create  sorbet/rbi/gems/[email protected]

  ...

All operations performed in working directory.
Please review changes and commit them.

This will load your application, find all the gems required by it and generate an RBI file for each gem under the sorbet/rbi/gems directory for each of those gems. This process will also import signatures that can be found inside each gem sources, and, optionally, any YARD documentation inside the gem.

$ tapioca help gem

Usage:
  tapioca gem [gem...]

Options:
  --out, -o,   [--outdir=directory]                                   # The output directory for generated gem RBI files
                                                                      # Default: sorbet/rbi/gems
               [--file-header], [--no-file-header]                    # Add a "This file is generated" header on top of each generated RBI file
                                                                      # Default: true
               [--all], [--no-all]                                    # Regenerate RBI files for all gems
  --pre, -b,   [--prerequire=file]                                    # A file to be required before Bundler.require is called
  --post, -a,  [--postrequire=file]                                   # A file to be required after Bundler.require is called
                                                                      # Default: sorbet/tapioca/require.rb
  -x,          [--exclude=gem [gem ...]]                              # Exclude the given gem(s) from RBI generation
  --typed, -t, [--typed-overrides=gem:level [gem:level ...]]          # Override for typed sigils for generated gem RBIs
                                                                      # Default: {"activesupport"=>"false"}
               [--verify], [--no-verify]                              # Verify RBIs are up-to-date
               [--doc], [--no-doc]                                    # Include YARD documentation from sources when generating RBIs. Warning: this might be slow
                                                                      # Default: true
               [--loc], [--no-loc]                                    # Include comments with source location when generating RBIs
                                                                      # Default: true
               [--exported-gem-rbis], [--no-exported-gem-rbis]        # Include RBIs found in the `rbi/` directory of the gem
                                                                      # Default: true
  -w,          [--workers=N]                                          # Number of parallel workers to use when generating RBIs (default: auto)
               [--auto-strictness], [--no-auto-strictness]            # Autocorrect strictness in gem RBIs in case of conflict with the DSL RBIs
                                                                      # Default: true
  --dsl-dir,   [--dsl-dir=directory]                                  # The DSL directory used to correct gems strictnesses
                                                                      # Default: sorbet/rbi/dsl
               [--rbi-max-line-length=N]                              # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
                                                                      # Default: 120
  -e,          [--environment=ENVIRONMENT]                            # The Rack/Rails environment to use when generating RBIs
                                                                      # Default: development
               [--halt-upon-load-error], [--no-halt-upon-load-error]  # Halt upon a load error while loading the Rails application
                                                                      # Default: true
  -c,          [--config=<config file path>]                          # Path to the Tapioca configuration file
                                                                      # Default: sorbet/tapioca/config.yml
  -V,          [--verbose], [--no-verbose]                            # Verbose output for debugging purposes

generate RBIs from gems

By default, running tapioca gem will only generate the RBI files for gems that have been added to or removed from the project's Gemfile this means that Tapioca will not regenerate the RBI files for untouched gems. However, when changing Tapioca configuration or bumping its version, it may be useful to force the regeneration of the RBI files previously generated. This can be done with the --all option:

bin/tapioca gems --all

Are you coming from srb rbi? See how tapioca gem compares to srb rbi.

Manually requiring parts of a gem

It may happen that the RBI file generated for a gem listed inside your Gemfile.lock is missing some definitions that you would expect it to be exporting.

For gems that have a normal default require and that load all of their constants through that, everything should work seamlessly. However, for gems that are marked as require: false in the Gemfile, or for gems that export constants optionally via different requires, where a single require does not load the whole gem code into memory, Tapioca will not be able to load some of the types into memory and, thus, won't be able to generate complete RBIs for them. For this reason, we need to keep a small external file named sorbet/tapioca/require.rb that is executed after all the gems in the Gemfile have been required and before generation of gem RBIs have started. This file is responsible for adding the requires for additional files from gems, which are not covered by the default require.

For example, suppose you are using the class BetterHtml::Parser exported from the better_html gem. Just doing a require "better_html" (which is the default require) does not load that type:

$ bundle exec pry

[1] pry(main)> require 'better_html'
=> true
[2] pry(main)> BetterHtml
=> BetterHtml
[3] pry(main)> BetterHtml::Parser
NameError: uninitialized constant BetterHtml::Parser
from (pry):3:in `__pry__`
[4] pry(main)> require 'better_html/parser'
=> true
[5] pry(main)> BetterHtml::Parser
=> BetterHtml::Parser

In order to make sure that tapioca can reflect on that type, we need to add the line require "better_html/parser" to the sorbet/tapioca/require.rb file. This will make sure BetterHtml::Parser is loaded into memory and a type annotation is generated for it in the better_html.rbi file. If this extra require line is not added to sorbet/tapioca/require.rb file, then Tapioca will be able to generate definitions for BetterHtml and other constants, but not for BetterHtml::Parser, which will be missing from the RBI file.

For example, you can take a look at Tapioca's own require.rb file:

# typed: strict
# frozen_string_literal: true

require "ansi/code"
require "google/protobuf"
require "rails/all"
require "rails/generators"
require "rails/generators/app_base"
require "rake/testtask"
require "rubocop/rake_task"

If you ever run into a case, where you add a gem or update the version of a gem and run tapioca gem but don't have some types you expect in the generated gem RBI files, you will need to make sure you have added the necessary requires to the sorbet/tapioca/require.rb file and regenerate the RBI file for that gem explicitly using bin/tapioca gem <gem-name>.

To help you get started, you can use the command tapioca require to auto-populate the contents of the sorbet/tapioca/require.rb file with all the requires found in your application:

$ bin/tapioca require

Compiling sorbet/tapioca/require.rb, this may take a few seconds... Done

All requires from this application have been written to sorbet/tapioca/require.rb.
Please review changes and commit them, then run `bin/tapioca gem`.

Once the file is generated, you should review it, remove all unnecessary requires and commit it.

Excluding a gem from RBI generation

It may be useful to exclude some gems from the generation process. For example for gems that are in Bundle's debug group or gems of which the contents are dependent on the architecture they are loaded on.

To do so you can pass the list of gems you want to exclude in the command line with the --exclude option:

$ bin/tapioca gems --exclude gemA gemB

Or through the configuration file:

gem:
  exclude:
    - gemA
    - gemB

There are a few development/test environment gems that can cause RBI generation issues, so Tapioca skips them by default:

  • debug
  • fakefs

Changing the strictness level of the RBI for a gem

By default, all RBI files for gems are generated with the strictness level typed: true. Sometimes, this strictness level can create type-checking errors when a gem contains definitions that conflict with Sorbet internal definitions for Ruby core and standard library.

Tapioca comes with an automatic detection (option --auto-strictness, enabled by default) of such cases and will switch the strictness level to typed: false in RBI files containing conflicts with the core and standard library definitions. It is nonetheless possible to manually switch the strictness level for a gem using the --typed-overrides option:

$ bin/tapioca gems --typed-overrides gemA:false gemB:false

Or through the configuration file:

gem:
  typed_overrides:
    gemA: "false"
    gemB: "false"

Keeping RBI files for gems up-to-date

To ensure all RBI files for gems are up-to-date with the latest changes in your Gemfile.lock, Tapioca provides a --verify option:

$ bin/tapioca gems --verify

Checking for out-of-date RBIs...

Nothing to do, all RBIs are up-to-date.

This option can be used on CI to make sure the RBI files are always up-to-date and ensure accurate type checking. Warning: doing so will break your normal Dependabot workflow as every pull-request opened to bump a gem version will fail CI since the RBI will be out-of-date and will require you to manually run bin/tapioca gems to update them.

Pulling RBI annotations from remote sources

Since Tapioca does not perform any type inference, the RBI files generated for the gems do not contain any type signatures. Instead, Tapioca relies on the community to provide high-quality, manually written RBI annotations for public gems.

To pull the annotations relevant to your project from the central repository, run the annotations command:

$ bin/tapioca annotations

Retrieving index from central repository... Done
Listing gems from Gemfile.lock... Done
Removing annotations for gems that have been removed...  Nothing to do
Fetching gem annotations from central repository...

  Fetched activesupport
   created  sorbet/rbi/annotations/activesupport.rbi

Done
$ tapioca help annotations

Usage:
  tapioca annotations

Options:
               [--sources=one two three]                      # URIs of the sources to pull gem RBI annotations from
                                                              # Default: ["https://raw.githubusercontent.com/Shopify/rbi-central/main"]
               [--netrc], [--no-netrc]                        # Use .netrc to authenticate to private sources
                                                              # Default: true
               [--netrc-file=NETRC_FILE]                      # Path to .netrc file
               [--auth=AUTH]                                  # HTTP authorization header for private sources
  --typed, -t, [--typed-overrides=gem:level [gem:level ...]]  # Override for typed sigils for pulled annotations
  -c,          [--config=<config file path>]                  # Path to the Tapioca configuration file
                                                              # Default: sorbet/tapioca/config.yml
  -V,          [--verbose], [--no-verbose]                    # Verbose output for debugging purposes

Pull gem RBI annotations from remote sources

By default, Tapioca will pull the annotations stored in the central repository located at https://github.com/Shopify/rbi-central. It is possible to use a custom repository by changing the value of the --sources options. For example if your repository is stored on Github:

$ bin/tapioca annotations --sources https://raw.githubusercontent.com/$USER/$REPO/$BRANCH

Tapioca also supports pulling annotations from multiple sources:

$ bin/tapioca annotations --sources https://raw.githubusercontent.com/$USER/$REPO1/$BRANCH https://raw.githubusercontent.com/$USER/$REPO2/$BRANCH

Basic authentication

Private repositories can be used as sources by passing the option --auth with an authentication string. For Github, this string is token $TOKEN where $TOKEN is a personal access token:

$ bin/tapioca annotations --sources https://raw.githubusercontent.com/$USER/$PRIVATE_REPO/$BRANCH --auth "token $TOKEN"

Using a .netrc file

Tapioca supports reading credentials from a netrc file (defaulting to ~/.netrc).

Given these lines in your netrc:

machine raw.githubusercontent.com
  login $USERNAME
  password $TOKEN

where $USERNAME is your Github username and $TOKEN is a personal access token, then, if you run Tapioca with the --netrc option (enabled by default), your annotation requests should be authenticated properly.

The --netrc-file option can be specified to read from a file other than ~/.netrc:

$ bin/tapioca annotations --netrc-file /path/to/my/netrc/file

Similar to --netrc-file, you can also specify an alternative netrc file by using the TAPIOCA_NETRC_FILE environment variable:

$ TAPIOCA_NETRC_FILE=/path/to/my/netrc/file bin/tapioca annotations

Tapioca will first try to find the netrc file as specified by the --netrc-file option. If that option is not supplied, it will try the TAPIOCA_NETRC_FILE environment variable value. If that value is not supplied either, it will fallback to ~/.netrc.

Changing the typed strictness of annotations files

Sometimes the annotations files pulled by Tapioca will create type errors in your project because of incompatibilities. It is possible to ignore such files by switching their strictness level --typed-overrides option:

$ bin/tapioca annotations --typed-overrides gemA:ignore gemB:false

Or through the configuration file:

annotations:
  typed_overrides:
    gemA: "ignore"
    gemB: "false"

Generating RBI files for Rails and other DSLs

Sorbet by itself does not understand DSLs involving meta-programming, such as Rails. This means that Sorbet won't know about constants and methods generated by ActiveRecord or ActiveSupport. To solve this, Tapioca can load your application and introspect it to find the constants and methods that would exist at runtime and compile them into RBI files.

To generate the RBI files for the DSLs used in your application, run the following command:

$ bin/tapioca dsl

Loading Rails application... Done
Loading DSL compiler classes... Done
Compiling DSL RBI files...

      create  sorbet/rbi/dsl/my_model.rbi
      ...

Done

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 compilers supplied by tapioca in the manual.

$ tapioca help dsl

Usage:
  tapioca dsl [constant...]

Options:
  --out, -o, [--outdir=directory]                                   # The output directory for generated DSL RBI files
                                                                    # Default: sorbet/rbi/dsl
             [--file-header], [--no-file-header]                    # Add a "This file is generated" header on top of each generated RBI file
                                                                    # Default: true
             [--only=compiler [compiler ...]]                       # Only run supplied DSL compiler(s)
             [--exclude=compiler [compiler ...]]                    # Exclude supplied DSL compiler(s)
             [--verify], [--no-verify]                              # Verifies RBIs are up-to-date
  -q,        [--quiet], [--no-quiet]                                # Suppresses file creation output
  -w,        [--workers=N]                                          # Number of parallel workers to use when generating RBIs (default: 2)
                                                                    # Default: 2
             [--rbi-max-line-length=N]                              # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
                                                                    # Default: 120
  -e,        [--environment=ENVIRONMENT]                            # The Rack/Rails environment to use when generating RBIs
                                                                    # Default: development
  -l,        [--list-compilers], [--no-list-compilers]              # List all loaded compilers
             [--app-root=APP_ROOT]                                  # The path to the Rails application
                                                                    # Default: .
             [--halt-upon-load-error], [--no-halt-upon-load-error]  # Halt upon a load error while loading the Rails application
                                                                    # Default: true
  -c,        [--config=<config file path>]                          # Path to the Tapioca configuration file
                                                                    # Default: sorbet/tapioca/config.yml
  -V,        [--verbose], [--no-verbose]                            # Verbose output for debugging purposes

generate RBIs for dynamic methods

Keeping RBI files for DSLs up-to-date

To ensure all RBI files for DSLs are up-to-date with the latest changes in your application or database, Tapioca provide a --verify option:

$ bin/tapioca dsl --verify

Loading Rails application... Done
Loading DSL compiler classes... Done
Checking for out-of-date RBIs...


RBI files are out-of-date. In your development environment, please run:
  `bin/tapioca dsl`
Once it is complete, be sure to commit and push any changes

Reason:
  File(s) changed:
  - sorbet/rbi/dsl/my_model.rbi

This option can be used on CI to make sure the RBI files are always up-to-date and ensure accurate type checking.

Writing custom DSL compilers

It is possible to create your own compilers for DSLs not supported by Tapioca out of the box.

Let's take for example this Encryptable module that uses the included hook to dynamically add a few methods to the classes that include it:

module Encryptable
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def attr_encrypted(attr_name)
      encrypted_attributes << attr_name

      attr_accessor(attr_name)

      encrypted_attr_name = :"#{attr_name}_encrypted"

      define_method(encrypted_attr_name) do
        value = send(attr_name)
        encrypt(value)
      end

      define_method("#{encrypted_attr_name}=") do |value|
        send("#{attr_name}=", decrypt(value))
      end
    end

    def encrypted_attributes
      @encrypted_attributes ||= []
    end
  end

  private

  def encrypt(value)
    value.unpack("H*").first
  end

  def decrypt(value)
    [value].pack("H*")
  end
end

When Encryptable is included in a class like this one, it makes it possible to call attr_encrypted to define an attribute, its accessors and its encrypted accessors:

class CreditCard
  include Encryptable

  attr_encrypted :number
end

These accessors can then be used on the CreditCard instance without having to define them in the class:

# typed: true
# file: example.rb

card = CreditCard.new
card.number = "1234 5678 9012 3456"

p card.number             # => "1234 5678 9012 3456"
p card.number_encrypted   # => "31323334203536373820393031322033343536"

card.number_encrypted = "31323334203536373820393031322033343536"
p card.number             # => "1234 5678 9012 3456"

Sadly, since these methods have been created dynamically at runtime, when our attr_encryptable method was run, there are no static traces of the number, number=, number_encrypted and number_encrypted= methods. Since Sorbet does not run the Ruby code but analyses it statically, it can't see these methods and running type-checking will show a bunch of errors:

$ bundle exec srb tc

lib/example.rb:5: Method number= does not exist on CreditCard https://srb.help/7003
lib/example.rb:7: Method number does not exist on CreditCard https://srb.help/7003
lib/example.rb:8: Method number_encrypted does not exist on CreditCard https://srb.help/7003
lib/example.rb:10: Method number_encrypted= does not exist on CreditCard https://srb.help/7003
lib/example.rb:11: Method number does not exist on CreditCard https://srb.help/7003

Errors: 5

To solve this you will have to create your own DSL compiler able that understands the Encryptable DSL and can generate the RBI definitions representing the actual shape of CreditCard at runtime.

To do so, you need to create a new DSL compiler similar to the following:

module Tapioca
  module Compilers
    class Encryptable < Tapioca::Dsl::Compiler
      extend T::Sig

      ConstantType = type_member {{ fixed: T.class_of(Encryptable) }}

      sig { override.returns(T::Enumerable[Module]) }
      def self.gather_constants
        # Collect all the classes that include Encryptable
        all_classes.select { |c| c < ::Encryptable }
      end

      sig { override.void }
      def decorate
        # Create a RBI definition for each class that includes Encryptable
        root.create_path(constant) do |klass|
          # For each encrypted attribute we find in the class
          constant.encrypted_attributes.each do |attr_name|
            # Create the RBI definitions for all the missing methods
            klass.create_method(attr_name, return_type: "String")
            klass.create_method("#{attr_name}=", parameters: [ create_param("value", type: "String") ], return_type: "void")
            klass.create_method("#{attr_name}_encrypted", return_type: "String")
            klass.create_method("#{attr_name}_encrypted=", parameters: [ create_param("value", type: "String") ], return_type: "void")
          end
        end
      end
    end
  end
end

In order for this DSL compiler to be discovered by Tapioca, it either needs to be placed inside the sorbet/tapioca/compilers directory of your application or be inside a tapioca/dsl/compilers folder on the load path. For example, if Encryptable was being exposed by a gem, all the gem needs to do is to place the DSL compiler inside the lib/tapioca/dsl/compilers folder and it will be automatically discovered and loaded by Tapioca.

There are two main parts to the DSL compiler API: gather_constants and decorate:

  • The gather_constants class method collects all classes (or modules) that should be processed by this specific DSL compiler.
  • The decorate method defines how to generate the necessary RBI definitions for the gathered constants.

Every compiler must declare the type member ConstantType in order for Sorbet to understand what the return type of the constant attribute reader is. It needs to be assigned the correct type variable matching the type of constants that gather_constants returns. This generic variable allows Sorbet to type-check method calls on the constant reader in your decorate method. See the Sorbet documentation on generics for more information.

You can now run the new RBI compiler through the normal DSL generation process (your custom compiler will be loaded automatically by Tapioca):

$ bin/tapioca dsl

Loading Rails application... Done
Loading DSL compiler classes... Done
Compiling DSL RBI files...

      create  sorbet/rbi/dsl/credit_card.rbi

Done

And then run Sorbet without error:

$ bundle exec srb tc

No errors! Great job.

For more concrete and advanced examples, take a look at Tapioca's default DSL compilers.

RBI files for missing constants and methods

Even after generating the RBIs, it is possible that some constants or methods are still undefined for Sorbet.

This might be for multiple reasons, with the most frequents ones being:

  • The constant or method comes from a part of the gem that Tapioca cannot load (optional dependency, wrong architecture, etc.)
  • The constant or method comes from a DSL or meta-programming that Tapioca doesn't support yet
  • The constant or method only exists when a specific code path is executed

The best way to deal with such occurrences is to manually create RBI files (shims) for them so you can also add types but depending on the amount of meta-programming used in your project this can mean an overwhelming amount of manual work.

Generating the RBI file for missing constants

To get you started quickly, Tapioca can create a RBI file containing a stub of all the missing constants so you can typecheck your project without missing constants and shim them later as you need them.

To generate the RBI file for the missing constants used in your application run the following command:

$ bin/tapioca todo

Compiling sorbet/rbi/todo.rbi, this may take a few seconds... Done
All unresolved constants have been written to sorbet/rbi/todo.rbi.
Please review changes and commit them.

This will generate the file sorbet/rbi/todo.rbi defining all unresolved constants as empty modules. Since the constants are "missing", Tapioca does not know if they should be marked as modules or classes and will use modules as a safer default. This file should be reviewed, corrected, if necessary, and then committed in your repository.

$ tapioca help todo

Usage:
  tapioca todo

Options:
      [--todo-file=TODO_FILE]              # Path to the generated todo RBI file
                                           # Default: sorbet/rbi/todo.rbi
      [--file-header], [--no-file-header]  # Add a "This file is generated" header on top of each generated RBI file
                                           # Default: true
  -c, [--config=<config file path>]        # Path to the Tapioca configuration file
                                           # Default: sorbet/tapioca/config.yml
  -V, [--verbose], [--no-verbose]          # Verbose output for debugging purposes

generate the list of unresolved constants

Manually writing RBI definitions (shims)

A shim is a hand-crafted RBI file that tells Sorbet about constants, ancestors, methods, etc. that it can't understand statically and aren't already generated by Tapioca.

These shims are usually placed in the sorbet/rbi/shims directory. From there, conventionally, you should follow the directory structure of the project to the file you'd like to shim. For example, say you had a person.rb file found at app/models/person.rb. If you were to add a shim for it, you'd want to create your RBI file at sorbet/rbi/shims/app/models/person.rbi.

A shim might be as simple as the class definition with an empty method body as below:

# typed: true

class Person
  sig { void }
  def some_method_sorbet_cannot_find; end
end

As you migrate to newer versions of Sorbet or Tapioca, some shims may become useless as Sorbet's internal definitions for Ruby's core and standard library is enhanced or Tapioca is able to generate definitions for new DSLs. To avoid keeping outdated or useless definitions inside your application shims, Tapioca provides the check-shims command:

$ bin/tapioca check-shims

Loading Sorbet payload...  Done
Loading shim RBIs from sorbet/rbi/shims...  Done
Loading gem RBIs from sorbet/rbi/gems...  Done
Loading gem RBIs from sorbet/rbi/dsl...  Done
Loading annotation RBIs from sorbet/rbi/annotations...  Done
Looking for duplicates...  Done

Duplicated RBI for ::MyModel#title:
  * sorbet/rbi/shims/my_model.rbi:2:2-2:14
  * sorbet/rbi/dsl/my_model.rbi:2:2-2:14

Duplicated RBI for ::String#capitalize:
  * https://github.com/sorbet/sorbet/tree/master/rbi/core/string.rbi#L406
  * sorbet/rbi/shims/core/string.rbi:3:2-3:23

Please remove the duplicated definitions from the sorbet/rbi/shims directory.

This command can be used on CI to make sure the RBI shims are always up-to-date and non-redundant with generated files.

$ tapioca help check_shims

Usage:
  tapioca check-shims

Options:
      [--gem-rbi-dir=GEM_RBI_DIR]                  # Path to gem RBIs
                                                   # Default: sorbet/rbi/gems
      [--dsl-rbi-dir=DSL_RBI_DIR]                  # Path to DSL RBIs
                                                   # Default: sorbet/rbi/dsl
      [--shim-rbi-dir=SHIM_RBI_DIR]                # Path to shim RBIs
                                                   # Default: sorbet/rbi/shims
      [--annotations-rbi-dir=ANNOTATIONS_RBI_DIR]  # Path to annotations RBIs
                                                   # Default: sorbet/rbi/annotations
      [--todo-rbi-file=TODO_RBI_FILE]              # Path to the generated todo RBI file
                                                   # Default: sorbet/rbi/todo.rbi
      [--payload], [--no-payload]                  # Check shims against Sorbet's payload
                                                   # Default: true
  -w, [--workers=N]                                # Number of parallel workers (default: auto)
  -c, [--config=<config file path>]                # Path to the Tapioca configuration file
                                                   # Default: sorbet/tapioca/config.yml
  -V, [--verbose], [--no-verbose]                  # Verbose output for debugging purposes

check duplicated definitions in shim RBIs

Configuration

Tapioca supports loading command defaults from a configuration file. The default configuration file location is sorbet/tapioca/config.yml but this default can be changed using the --config flag and supplying an alternative configuration file path.

Tapioca's configuration file must be a well-formed YAML file with top-level keys for the various Tapioca commands. Keys under each such top-level command should be the underscore version of a long option name for that command and the value for that key should be the value of the option.

For example, if you always want to generate gem RBIs with inline documentation, then you would create the file sorbet/tapioca/config.yml as:

gem:
  doc: true

Additionally, if you always want to exclude the AASM and ActiveRecordFixtures DSL compilers in your DSL RBI generation runs, your config file would then look like this:

gem:
  doc: true
dsl:
  exclude:
  - UrlHelpers
  - ActiveRecordFixtures

The full configuration file, with each option and its default value, would look something like this:

---
require:
  postrequire: sorbet/tapioca/require.rb
todo:
  todo_file: sorbet/rbi/todo.rbi
  file_header: true
dsl:
  outdir: sorbet/rbi/dsl
  file_header: true
  only: []
  exclude: []
  verify: false
  quiet: false
  workers: 2
  rbi_max_line_length: 120
  environment: development
  list_compilers: false
  app_root: "."
  halt_upon_load_error: true
gem:
  outdir: sorbet/rbi/gems
  file_header: true
  all: false
  prerequire: ''
  postrequire: sorbet/tapioca/require.rb
  exclude: []
  typed_overrides:
    activesupport: 'false'
  verify: false
  doc: true
  loc: true
  exported_gem_rbis: true
  workers: 1
  auto_strictness: true
  dsl_dir: sorbet/rbi/dsl
  rbi_max_line_length: 120
  environment: development
  halt_upon_load_error: true
check_shims:
  gem_rbi_dir: sorbet/rbi/gems
  dsl_rbi_dir: sorbet/rbi/dsl
  shim_rbi_dir: sorbet/rbi/shims
  annotations_rbi_dir: sorbet/rbi/annotations
  todo_rbi_file: sorbet/rbi/todo.rbi
  payload: true
  workers: 1
annotations:
  sources:
  - https://raw.githubusercontent.com/Shopify/rbi-central/main
  netrc: true
  netrc_file: ''
  typed_overrides: {}

Contributing

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 code of conduct.

License

The gem is available as open source under the terms of the MIT License.

More Repositories

1

draggable

The JavaScript Drag & Drop library your grandparents warned you about.
JavaScript
17,927
star
2

dashing

The exceptionally handsome dashboard framework in Ruby and Coffeescript.
JavaScript
11,025
star
3

liquid

Liquid markup language. Safe, customer facing template language for flexible web apps.
Ruby
10,419
star
4

toxiproxy

⏰ 🔥 A TCP proxy to simulate network and system conditions for chaos and resiliency testing
Go
9,412
star
5

react-native-skia

High-performance React Native Graphics using Skia
TypeScript
6,746
star
6

flash-list

A better list for React Native
TypeScript
5,489
star
7

polaris

Shopify’s design system to help us work together to build a great experience for all of our merchants.
TypeScript
5,352
star
8

hydrogen-v1

React-based framework for building dynamic, Shopify-powered custom storefronts.
TypeScript
3,747
star
9

go-lua

A Lua VM in Go
Go
2,773
star
10

bootsnap

Boot large Ruby/Rails apps faster
Ruby
2,614
star
11

graphql-design-tutorial

2,335
star
12

restyle

A type-enforced system for building UI components in React Native with TypeScript.
TypeScript
2,331
star
13

dawn

Shopify's first source available reference theme, with Online Store 2.0 features and performance built-in.
Liquid
2,279
star
14

identity_cache

IdentityCache is a blob level caching solution to plug into Active Record. Don't #find, #fetch!
Ruby
1,874
star
15

quilt

A loosely related set of packages for JavaScript/TypeScript projects at Shopify
TypeScript
1,703
star
16

shopify_app

A Rails Engine for building Shopify Apps
Ruby
1,649
star
17

kubeaudit

kubeaudit helps you audit your Kubernetes clusters against common security controls
Go
1,624
star
18

shipit-engine

Deployment coordination
Ruby
1,406
star
19

graphql-batch

A query batching executor for the graphql gem
Ruby
1,388
star
20

packwerk

Good things come in small packages.
Ruby
1,346
star
21

krane

A command-line tool that helps you ship changes to a Kubernetes namespace and understand the result
Ruby
1,309
star
22

semian

🐒 Resiliency toolkit for Ruby for failing fast
Ruby
1,286
star
23

slate

Slate is a toolkit for developing Shopify themes. It's designed to assist your workflow and speed up the process of developing, testing, and deploying themes.
JavaScript
1,283
star
24

ejson

EJSON is a small library to manage encrypted secrets using asymmetric encryption.
Go
1,246
star
25

superdb

The Super Debugger, a realtime wireless debugger for iOS
Objective-C
1,158
star
26

shopify_python_api

ShopifyAPI library allows Python developers to programmatically access the admin section of stores
Python
1,072
star
27

storefront-api-examples

Example custom storefront applications built on Shopify's Storefront API
JavaScript
1,069
star
28

themekit

Shopify theme development command line tool.
Go
1,068
star
29

Timber

The ultimate Shopify theme framework, built by Shopify.
Liquid
992
star
30

shopify-cli

Shopify CLI helps you build against the Shopify platform faster.
Ruby
987
star
31

shopify-api-ruby

ShopifyAPI is a lightweight gem for accessing the Shopify admin REST and GraphQL web services.
Ruby
982
star
32

hydrogen

Hydrogen is Shopify’s stack for headless commerce. It provides a set of tools, utilities, and best-in-class examples for building dynamic and performant commerce applications. Hydrogen is designed to dovetail with Remix, Shopify’s full stack web framework, but it also provides a React library portable to other supporting frameworks. Demo store 👇🏼
TypeScript
966
star
33

js-buy-sdk

The JS Buy SDK is a lightweight library that allows you to build ecommerce into any website. It is based on Shopify's API and provides the ability to retrieve products and collections from your shop, add products to a cart, and checkout.
JavaScript
932
star
34

job-iteration

Makes your background jobs interruptible and resumable by design.
Ruby
907
star
35

cli-ui

Terminal user interface library
Ruby
869
star
36

react-native-performance

Performance monitoring for React Native apps
TypeScript
860
star
37

ruby-lsp

An opinionated language server for Ruby
Ruby
851
star
38

active_shipping

ActiveShipping is a simple shipping abstraction library extracted from Shopify
Ruby
809
star
39

shopify-api-js

Shopify Admin API Library for Node. Accelerate development with support for authentication, graphql proxy, webhooks
TypeScript
765
star
40

maintenance_tasks

A Rails engine for queueing and managing data migrations.
Ruby
705
star
41

shopify-app-template-node

JavaScript
701
star
42

remote-ui

TypeScript
701
star
43

erb_lint

Lint your ERB or HTML files
Ruby
651
star
44

shopify_theme

A console tool for interacting with Shopify Theme Assets.
Ruby
640
star
45

pitchfork

Ruby
630
star
46

ghostferry

The swiss army knife of live data migrations
Go
596
star
47

yjit

Optimizing JIT compiler built inside CRuby
593
star
48

statsd-instrument

A StatsD client for Ruby apps. Provides metaprogramming methods to inject StatsD instrumentation into your code.
Ruby
546
star
49

autotuner

Get suggestions to tune Ruby's garbage collector
Ruby
511
star
50

shopify.github.com

A collection of the open source projects by Shopify
CSS
505
star
51

ruby-style-guide

Shopify’s Ruby Style Guide
Ruby
475
star
52

theme-scripts

Theme Scripts is a collection of utility libraries which help theme developers with problems unique to Shopify Themes.
JavaScript
470
star
53

livedata-ktx

Kotlin extension for LiveData, chaining like RxJava
Kotlin
468
star
54

starter-theme

The Shopify Themes Team opinionated starting point for new a Slate project
Liquid
459
star
55

shopify-demo-app-node-react

JavaScript
444
star
56

web-configs

Common configurations for building web apps at Shopify
JavaScript
433
star
57

mobile-buy-sdk-ios

Shopify’s Mobile Buy SDK makes it simple to sell physical products inside your mobile app. With a few lines of code, you can connect your app with the Shopify platform and let your users buy your products using Apple Pay or their credit card.
Swift
433
star
58

shopify_django_app

Get a Shopify app up and running with Django and Python Shopify API
Python
425
star
59

deprecation_toolkit

⚒Eliminate deprecations from your codebase ⚒
Ruby
390
star
60

ruby-lsp-rails

A Ruby LSP extension for Rails
Ruby
388
star
61

bootboot

Dualboot your Ruby app made easy
Ruby
374
star
62

FunctionalTableData

Declarative UITableViewDataSource implementation
Swift
365
star
63

shadowenv

reversible directory-local environment variable manipulations
Rust
349
star
64

shopify-node-app

An example app that uses Polaris components and shopify-express
JavaScript
327
star
65

polaris-viz

A collection of React and React native components that compose Shopify's data visualization system
TypeScript
317
star
66

better-html

Better HTML for Rails
Ruby
311
star
67

theme-check

The Ultimate Shopify Theme Linter
Ruby
306
star
68

product-reviews-sample-app

A sample Shopify application that creates and stores product reviews for a store, written in Node.js
JavaScript
300
star
69

tracky

The easiest way to do motion tracking!
Swift
295
star
70

shopify-api-php

PHP
279
star
71

measured

Encapsulate measurements and their units in Ruby.
Ruby
275
star
72

cli

Build apps, themes, and hydrogen storefronts for Shopify
TypeScript
273
star
73

money

Manage money in Shopify with a class that won't lose pennies during division
Ruby
265
star
74

javascript

The home for all things JavaScript at Shopify.
253
star
75

ruvy

Rust
252
star
76

limiter

Simple Ruby rate limiting mechanism.
Ruby
244
star
77

vscode-ruby-lsp

VS Code plugin for connecting with the Ruby LSP
TypeScript
232
star
78

ruby_memcheck

Use Valgrind memcheck on your native gem without going crazy
Ruby
230
star
79

polaris-tokens

Design tokens for Polaris, Shopify’s design system
TypeScript
230
star
80

buy-button-js

BuyButton.js is a highly customizable UI library for adding ecommerce functionality to any website.
JavaScript
230
star
81

android-testify

Add screenshots to your Android tests
Kotlin
225
star
82

spoom

Useful tools for Sorbet enthusiasts
Ruby
220
star
83

turbograft

Hard fork of turbolinks, adding partial page replacement strategies, and utilities.
JavaScript
213
star
84

mobile-buy-sdk-android

Shopify’s Mobile Buy SDK makes it simple to sell physical products inside your mobile app. With a few lines of code, you can connect your app with the Shopify platform and let your users buy your products using their credit card.
Java
202
star
85

graphql-js-client

A Relay compliant GraphQL client.
JavaScript
187
star
86

shopify-app-template-php

PHP
186
star
87

skeleton-theme

A barebones ☠️starter theme with the required files needed to compile with Slate and upload to Shopify.
Liquid
185
star
88

sprockets-commoner

Use Babel in Sprockets to compile JavaScript modules for the browser
Ruby
182
star
89

rotoscope

High-performance logger of Ruby method invocations
Ruby
180
star
90

shopify-app-template-remix

TypeScript
178
star
91

git-chain

Tool to rebase multiple Git branches based on the previous one.
Ruby
176
star
92

verdict

Framework to define and implement A/B tests in your application, and collect data for analysis purposes.
Ruby
176
star
93

hydrogen-react

Reusable components and utilities for building Shopify-powered custom storefronts.
TypeScript
174
star
94

ui-extensions

TypeScript
173
star
95

storefront-api-learning-kit

JavaScript
171
star
96

heap-profiler

Ruby heap profiler
C++
159
star
97

autoload_reloader

Experimental implementation of code reloading using Ruby's autoload
Ruby
158
star
98

app_profiler

Collect performance profiles for your Rails application.
Ruby
157
star
99

graphql-metrics

Extract as much much detail as you want from GraphQL queries, served up from your Ruby app and the graphql gem.
Ruby
157
star
100

active_fulfillment

Active Merchant library for integration with order fulfillment services
Ruby
155
star