lib/steep/index/source_index.rb



module Steep
  module Index
    class SourceIndex
      class ConstantEntry
        attr_reader :name

        attr_reader :definitions
        attr_reader :references

        def initialize(name:)
          @name = name

          @definitions = Set[].compare_by_identity
          @references = Set[].compare_by_identity
        end

        def add_definition(node)
          case node.type
          when :casgn, :class, :module
            @definitions << node
          else
            raise "Unexpected constant definition: #{node.type}"
          end

          self
        end

        def add_reference(node)
          case node.type
          when :const
            @references << node
          else
            raise "Unexpected constant reference: #{node.type}"
          end

          self
        end

        def merge!(other)
          definitions.merge(other.definitions)
          references.merge(other.references)
          self
        end
      end

      class MethodEntry
        attr_reader :name

        attr_reader :definitions
        attr_reader :references

        def initialize(name:)
          @name = name

          @definitions = Set[].compare_by_identity
          @references = Set[].compare_by_identity
        end

        def add_definition(node)
          case node.type
          when :def, :defs
            @definitions << node
          else
            raise "Unexpected method definition: #{node.type}"
          end

          self
        end

        def add_reference(node)
          case node.type
          when :send, :block
            @references << node
          else
            raise "Unexpected method reference: #{node.type}"
          end

          self
        end

        def merge!(other)
          definitions.merge(other.definitions)
          references.merge(other.references)
          self
        end
      end

      attr_reader :source
      attr_reader :constant_index
      attr_reader :method_index

      attr_reader :parent
      attr_reader :count
      attr_reader :parent_count

      def initialize(source:, parent: nil)
        @source = source
        @parent = parent
        @parent_count = parent&.count

        @count = @parent_count || 0

        @constant_index = {}
        @method_index = {}
      end

      def new_child
        SourceIndex.new(source: source, parent: self)
      end

      def merge!(child)
        raise unless child.parent == self
        raise unless child.parent_count == count

        constant_index.merge!(child.constant_index) do |_, entry, child_entry|
          entry.merge!(child_entry)
        end

        method_index.merge!(child.method_index) do |_, entry, child_entry|
          entry.merge!(child_entry)
        end

        @count = child.count + 1
      end

      def add_definition(constant: nil, method: nil, definition:)
        @count += 1
        entry(constant: constant, method: method).add_definition(definition)
        self
      end

      def add_reference(constant: nil, method: nil, ref:)
        @count += 1
        entry(constant: constant, method: method).add_reference(ref)
        self
      end

      def entry(constant: nil, method: nil)
        case
        when constant
          constant_index[constant] ||= ConstantEntry.new(name: constant)
        when method
          method_index[method] ||= MethodEntry.new(name: method)
        else
          raise
        end
      end
    end
  end
end