lib/solargraph/pin/parameter.rb



# frozen_string_literal: true


module Solargraph
  module Pin
    class Parameter < LocalVariable
      # @return [Symbol]

      attr_reader :decl

      # @return [String]

      attr_reader :asgn_code

      # @param decl [::Symbol] :arg, :optarg, :kwarg, :kwoptarg, :restarg, :kwrestarg, :block, :blockarg

      # @param asgn_code [String, nil]

      # @param return_type [ComplexType, nil]

      def initialize decl: :arg, asgn_code: nil, return_type: nil, **splat
        super(**splat)
        @asgn_code = asgn_code
        @decl = decl
        @return_type = return_type
      end

      def keyword?
        [:kwarg, :kwoptarg].include?(decl)
      end

      def kwrestarg?
        decl == :kwrestarg || (assignment && [:HASH, :hash].include?(assignment.type))
      end

      def arg?
        decl == :arg
      end

      def restarg?
        decl == :restarg
      end

      def rest?
        decl == :restarg || decl == :kwrestarg
      end

      def block?
        [:block, :blockarg].include?(decl)
      end

      def to_rbs
        case decl
        when :optarg
          "?#{super}"
        when :kwarg
          "#{name}: #{return_type.to_rbs}"
        when :kwoptarg
          "?#{name}: #{return_type.to_rbs}"
        when :restarg
          "*#{super}"
        when :kwrestarg
          "**#{super}"
        else
          super
        end
      end

      # @return [String]

      def full
        case decl
        when :optarg
          "#{name} = #{asgn_code || '?'}"
        when :kwarg
          "#{name}:"
        when :kwoptarg
          "#{name}: #{asgn_code || '?'}"
        when :restarg
          "*#{name}"
        when :kwrestarg
          "**#{name}"
        when :block, :blockarg
          "&#{name}"
        else
          name
        end
      end

      # @return [ComplexType]

      def return_type
        if @return_type.nil?
          @return_type = ComplexType::UNDEFINED
          found = param_tag
          @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil?
          if @return_type.undefined?
            if decl == :restarg
              @return_type = ComplexType.try_parse('::Array')
            elsif decl == :kwrestarg
              @return_type = ComplexType.try_parse('::Hash')
            elsif decl == :blockarg
              @return_type = ComplexType.try_parse('::Proc')
            end
          end
        end
        super
        @return_type
      end

      # The parameter's zero-based location in the block's signature.

      #

      # @return [Integer]

      def index
        # @type [Method, Block]

        method_pin = closure
        method_pin.parameter_names.index(name)
      end

      # @param api_map [ApiMap]

      def typify api_map
        return return_type.qualify(api_map, closure.context.namespace) unless return_type.undefined?
        closure.is_a?(Pin::Block) ? typify_block_param(api_map) : typify_method_param(api_map)
      end

      def documentation
        tag = param_tag
        return '' if tag.nil? || tag.text.nil?
        tag.text
      end

      def try_merge! pin
        return false unless super && closure == pin.closure
        true
      end

      private

      # @return [YARD::Tags::Tag, nil]

      def param_tag
        params = closure.docstring.tags(:param)
        params.each do |p|
          return p if p.name == name
        end
        params[index] if index && params[index] && (params[index].name.nil? || params[index].name.empty?)
      end

      # @param api_map [ApiMap]

      # @return [ComplexType]

      def typify_block_param api_map
        if closure.is_a?(Pin::Block) && closure.receiver
          return closure.typify_parameters(api_map)[index]
        end
        ComplexType::UNDEFINED
      end

      # @param api_map [ApiMap]

      # @return [ComplexType]

      def typify_method_param api_map
        meths = api_map.get_method_stack(closure.full_context.tag, closure.name, scope: closure.scope)
        # meths.shift # Ignore the first one

        meths.each do |meth|
          found = nil
          params = meth.docstring.tags(:param) + see_reference(docstring, api_map)
          params.each do |p|
            next unless p.name == name
            found = p
            break
          end
          if found.nil? and !index.nil?
            found = params[index] if params[index] && (params[index].name.nil? || params[index].name.empty?)
          end
          return ComplexType.try_parse(*found.types).qualify(api_map, meth.context.namespace) unless found.nil? || found.types.nil?
        end
        ComplexType::UNDEFINED
      end

      # @param heredoc [YARD::Docstring]

      # @param api_map [ApiMap]

      # @param skip [::Array]

      # @return [::Array<YARD::Tags::Tag>]

      def see_reference heredoc, api_map, skip = []
        heredoc.ref_tags.each do |ref|
          next unless ref.tag_name == 'param' && ref.owner
          result = resolve_reference(ref.owner.to_s, api_map, skip)
          return result unless result.nil?
        end
        []
      end

      # @param ref [String]

      # @param api_map [ApiMap]

      # @param skip [::Array]

      # @return [::Array<YARD::Tags::Tag>, nil]

      def resolve_reference ref, api_map, skip
        return nil if skip.include?(ref)
        skip.push ref
        parts = ref.split(/[\.#]/)
        if parts.first.empty?
          path = "#{namespace}#{ref}"
        else
          fqns = api_map.qualify(parts.first, namespace)
          return nil if fqns.nil?
          path = fqns + ref[parts.first.length] + parts.last
        end
        pins = api_map.get_path_pins(path)
        pins.each do |pin|
          params = pin.docstring.tags(:param)
          return params unless params.empty?
        end
        pins.each do |pin|
          params = see_reference(pin.docstring, api_map, skip)
          return params unless params.empty?
        end
        nil
      end
    end
  end
end