lib/find-gem-rbis.rb



#!/usr/bin/env ruby
# typed: false

require_relative './step_interface'
require_relative './t'

require 'bundler'
require 'fileutils'
require 'set'
require 'digest'

class Sorbet; end
module Sorbet::Private; end
class Sorbet::Private::FindGemRBIs
  XDG_CACHE_HOME = ENV['XDG_CACHE_HOME'] || "#{ENV['HOME']}/.cache"
  RBI_CACHE_DIR = "#{XDG_CACHE_HOME}/sorbet/gem-rbis/"
  GEM_DIR = 'rbi'

  HEADER = Sorbet::Private::Serialize.header(false, 'find-gem-rbis')

  include Sorbet::Private::StepInterface

  # List of rbi folders in the gem's source
  T::Sig::WithoutRuntime.sig {params(gemspec: T.untyped).returns(T.nilable(String))}
  def self.paths_within_gem_sources(gemspec)
    gem_rbi = "#{gemspec.full_gem_path}/#{GEM_DIR}"
    gem_rbi if Dir.exist?(gem_rbi)
  end

  T::Sig::WithoutRuntime.sig {void}
  def self.main
    FileUtils.mkdir_p(RBI_CACHE_DIR) unless Dir.exist?(RBI_CACHE_DIR)
    output_file = File.exist?('Gemfile.lock') ? RBI_CACHE_DIR + Digest::MD5.hexdigest(File.read('Gemfile.lock')) : nil
    return unless output_file
    gemspecs = Bundler.load.specs.sort_by(&:name)

    gem_source_paths = T.let([], T::Array[String])

    # Tapioca (https://github.com/Shopify/tapioca) does not require gems `rbi/` folders
    # to be passed to Sorbet as it copies the signatures at RBI generation time.
    #
    # To avoid conflicts between RBIs generated with tapioca and the one provided by the
    # gem, we disable the `rbi/` caching and let Sorbet use the RBIs from `sorbet/rbi/gems`.

    unless gemspecs.any? { |g| g.name == 'tapioca' }
      gemspecs.each do |gemspec|
        gem_source_paths << paths_within_gem_sources(gemspec)
      end
    end

    File.write(output_file, gem_source_paths.compact.join("\n"))
  end

  def self.output_file
    nil
  end
end

if $PROGRAM_NAME == __FILE__
  Sorbet::Private::FindGemRBIs.main
end