lib/solargraph/gem_pins.rb



# frozen_string_literal: true


require 'rbs'

module Solargraph
  # A utility for building gem pins from a combination of YARD and RBS

  # documentation.

  #

  module GemPins
    # Build an array of pins from a gem specification. The process starts with

    # YARD, enhances the resulting pins with RBS definitions, and appends RBS

    # pins that don't exist in the YARD mapping.

    #

    # @param gemspec [Gem::Specification]

    # @return [Array<Pin::Base>]

    def self.build(gemspec)
      yard_pins = build_yard_pins(gemspec)
      rbs_map = RbsMap.from_gemspec(gemspec)
      combine yard_pins, rbs_map
    end

    # @param yard_pins [Array<Pin::Base>]

    # @param rbs_map [RbsMap]

    # @return [Array<Pin::Base>]

    def self.combine(yard_pins, rbs_map)
      in_yard = Set.new
      combined = yard_pins.map do |yard|
        in_yard.add yard.path
        next yard unless yard.is_a?(Pin::Method)

        rbs = rbs_map.path_pin(yard.path, Pin::Method)
        next yard unless rbs

        # @sg-ignore

        yard.class.new(
          location: yard.location,
          closure: yard.closure,
          name: yard.name,
          comments: yard.comments,
          scope: yard.scope,
          parameters: rbs.parameters,
          generics: rbs.generics,
          node: yard.node,
          signatures: yard.signatures,
          return_type: best_return_type(rbs.return_type, yard.return_type)
        )
      end
      in_rbs = rbs_map.pins.reject { |pin| in_yard.include?(pin.path) }
      combined + in_rbs
    end

    class << self
      private

      # @param gemspec [Gem::Specification]

      # @return [Array<Pin::Base>]

      def build_yard_pins(gemspec)
        Yardoc.cache(gemspec) unless Yardoc.cached?(gemspec)
        yardoc = Yardoc.load!(gemspec)
        YardMap::Mapper.new(yardoc, gemspec).map
      end

      # Select the first defined type.

      #

      # @param choices [Array<ComplexType>]

      # @return [ComplexType]

      def best_return_type *choices
        choices.find { |pin| pin.defined? } || choices.first || ComplexType::UNDEFINED
      end
    end
  end
end