RBS::Patch

RBS::Patch manages RBS (Ruby Signature) type definitions through patches. It applies incremental changes to existing RBS signatures.

Supported Operations

  • override: Replace an existing method signature
  • delete: Remove a method signature
  • append_after: Insert a method signature after a specified method
  • prepend_before: Insert a method signature before a specified method

All operations use RBS annotations (e.g., %a{patch:override}), keeping patch files valid RBS syntax.

Installation

Install the gem and add to the application's Gemfile by executing:

bundle add rbs-patch

If bundler is not being used to manage dependencies, install the gem by executing:

gem install rbs-patch

Usage

Basic Usage

# Apply patches to RBS files
rbs-patch base.rbs patch1.rbs patch2.rbs

# Mix files and directories
rbs-patch lib/types/ sig/patches/

# Output goes to stdout - redirect to save
rbs-patch base.rbs patch.rbs > output.rbs

Programmatic Usage

require 'rbs/patch'

p = RBS::Patch.new

# Load from a single file
p.apply(path: Pathname("sig/user.rbs"))

# Load from a directory (all .rbs files)
p.apply(path: Pathname("sig/patches"))

# Apply from string
p.apply(<<~RBS)
  class User
    %a{patch:override}
    def name: () -> String?
  end
RBS

puts p.to_s

Annotation Syntax

All patch operations use RBS annotations with the format %a{patch:operation} or %a{patch:operation:target}.

Method-level Operations

override - Replace existing method signature
class User
  %a{patch:override}
  def name: () -> String?  # Replaces existing method signature at the same position
end
delete - Remove method signature
class User
  %a{patch:delete}
  def email: () -> String  # Removes this method from the class
end
append_after(method_name) - Insert method after specified method
class User
  %a{patch:append_after(name)}
  def nickname: () -> String?  # Inserts after the 'name' method
end
prepend_before(method_name) - Insert method before specified method
class User
  %a{patch:prepend_before(name)}
  def id: () -> Integer  # Inserts before the 'name' method
end

Class/Module-level Operations

override - Replace entire class/module
%a{patch:override}
class User
  def name: () -> String  # Completely replaces the User class definition
end
delete - Remove class/module
%a{patch:delete}
class User
end  # Removes the entire User class
append_after(ClassName) - Insert class/module after specified class
%a{patch:append_after(User)}
class Admin
  def permissions: () -> Array[String]
end  # Inserts Admin class after User class
prepend_before(ClassName) - Insert class/module before specified class
%a{patch:prepend_before(User)}
class Guest
  def readonly: () -> bool
end  # Inserts Guest class before User class

Comment Handling in override

When override replaces a method, class, or module, the comment on the two sides is merged rather than always taken from one side:

  • If the overriding declaration has a comment, it replaces the original comment.
  • If the overriding declaration has no comment, the original comment (if any) is kept.
p.apply(<<~RBS)
  class User
    # The user's display name.
    def name: () -> String
  end
RBS

p.apply(<<~RBS)
  class User
    %a{patch:override}
    def name: () -> String?  # No comment here, so the original one is kept
  end
RBS

# Result:
# class User
#   # The user's display name.
#   def name: () -> String?
# end

There is currently no way to explicitly clear an existing comment through override — omitting the comment always keeps the original one.

Note: In RBS syntax, a comment must be written before the annotation, not after it:

# This comment is attached to the method
%a{patch:override}
def name: () -> String?

Working with Nested Modules

Operations work correctly within nested module structures:

module MyApp
  module Models
    %a{patch:append_after(User)}
    class Admin
      def role: () -> String
    end
  end
end

Merging Multiple Definitions

Without annotations, multiple class definitions are merged:

p.apply(<<~RBS)
  class User
    def name: () -> String
  end
RBS

p.apply(<<~RBS)
  class User
    def email: () -> String  # Adds to existing User class
  end
RBS

# Result:
# class User
#   def name: () -> String
#   def email: () -> String
# end

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 the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/kozy4324/rbs-patch.

License

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