lib/solargraph/yard_map/rdoc_to_yard.rb



# frozen_string_literal: true


require 'rdoc'
require 'rdoc/rdoc'
require 'tmpdir'
require 'fileutils'

module Solargraph
  class YardMap
    module RdocToYard
      extend ApiMap::SourceToYard

      # @param spec [Gem::Specification]

      def self.run spec
        Dir.mktmpdir do |tmpdir|
          rdir = File.join(tmpdir, 'rdoc')
          Dir.chdir spec.full_gem_path do
            pins = []
            pins.push Solargraph::Pin::ROOT_PIN
            name_hash = {}
            cmd = "rdoc -q -N -r -o #{rdir}"
            spec.load_paths.each do |path|
              cmd += " -i #{path}"
            end
            `#{cmd}`
            store = RDoc::Store.new(rdir)
            store.load_all
            store.cache[:modules].each do |mod|
              # store.load_class(mod)

              # @type [RDoc::NormalClass]

              mod = store.find_class_or_module(mod)
              closure = pins.select { |pin| pin.path == mod.full_name.split('::')[0..-2].join('::') }.first || pins.first
              namepin = Solargraph::Pin::Namespace.new(
                type: (mod.module? ? :module : :class),
                name: mod.name,
                comments: commentary(mod.comment),
                closure: closure,
                location: locate(mod)
              )
              mod.parse(mod.comment_location)
              # @param inc [RDoc::Include]

              mod.includes.each do |inc|
                pins.push Solargraph::Pin::Reference::Include.new(
                  location: locate(inc),
                  name: inc.name,
                  closure: namepin
                )
              end
              # @param inc [RDoc::Extend]

              mod.extends.each do |ext|
                pins.push Solargraph::Pin::Reference::Extend.new(
                  location: locate(ext),
                  name: ext.name,
                  closure: namepin
                )
              end
              pins.push namepin
              name_hash[mod.full_name] = namepin
              # @param met [RDoc::AnyMethod]

              mod.each_method do |met|
                pin = Solargraph::SourceMap.load_string("def Object.tmp#{met.param_seq};end").first_pin('Object.tmp') || Solargraph::Pin::BaseMethod.new
                pins.push Solargraph::Pin::Method.new(
                  name: met.name,
                  closure: namepin,
                  comments: commentary(met.comment),
                  scope: met.type.to_sym,
                  args: pin.parameters,
                  visibility: met.visibility,
                  location: locate(met)
                )
              end
              # @param const [RDoc::Constant]

              mod.each_constant do |const|
                pins.push Solargraph::Pin::Constant.new(
                  name: const.name,
                  closure: namepin,
                  comments: commentary(const.comment),
                  location: locate(const)
                )
              end
            end
            mapstore = Solargraph::ApiMap::Store.new(pins)
            rake_yard(mapstore)
            YARD::Registry.clear
            code_object_map.values.each do |co|
              YARD::Registry.register(co)
            end
            cache_dir = File.join(Solargraph::YardMap::CoreDocs.cache_dir, 'gems', "#{spec.name}-#{spec.version}", "yardoc")
            FileUtils.remove_entry_secure cache_dir if File.exist?(cache_dir)
            FileUtils.mkdir_p cache_dir
            # @todo Should merge be true?

            YARD::Registry.save true, cache_dir
          end
        end
      end

      def self.base_name mod
        mod.full_name.split('::')[0..-2].join('::')
      end

      def self.commentary cmnt
        result = []
        cmnt.parts.each do |part|
          result.push RDoc::Markup::ToHtml.new({}).to_html(part.text) if part.respond_to?(:text)
        end
        result.join("\n\n")
      end

      # @param obj [RDoc::Context]

      def self.locate obj
        # @todo line is always nil for some reason

        file, line = find_file(obj)
        return nil if file.nil?
        Location.new(
          file,
          Range.from_to(line || 1, 0, line || 1, 0)
        )
      end

      def self.find_file obj
        if obj.respond_to?(:in_files) && !obj.in_files.empty?
          [obj.in_files.first.to_s.sub(/^file /, ''), obj.in_files.first.line]
        else
          [obj.file_name, obj.line]
        end
      end
    end
  end
end