lib/ruby_lsp/listeners/document_highlight.rb



# typed: strict
# frozen_string_literal: true

module RubyLsp
  module Listeners
    class DocumentHighlight
      include Requests::Support::Common

      GLOBAL_VARIABLE_NODES = [
        Prism::GlobalVariableAndWriteNode,
        Prism::GlobalVariableOperatorWriteNode,
        Prism::GlobalVariableOrWriteNode,
        Prism::GlobalVariableReadNode,
        Prism::GlobalVariableTargetNode,
        Prism::GlobalVariableWriteNode,
      ] #: Array[singleton(Prism::Node)]

      INSTANCE_VARIABLE_NODES = [
        Prism::InstanceVariableAndWriteNode,
        Prism::InstanceVariableOperatorWriteNode,
        Prism::InstanceVariableOrWriteNode,
        Prism::InstanceVariableReadNode,
        Prism::InstanceVariableTargetNode,
        Prism::InstanceVariableWriteNode,
      ] #: Array[singleton(Prism::Node)]

      CONSTANT_NODES = [
        Prism::ConstantAndWriteNode,
        Prism::ConstantOperatorWriteNode,
        Prism::ConstantOrWriteNode,
        Prism::ConstantReadNode,
        Prism::ConstantTargetNode,
        Prism::ConstantWriteNode,
      ] #: Array[singleton(Prism::Node)]

      CONSTANT_PATH_NODES = [
        Prism::ConstantPathAndWriteNode,
        Prism::ConstantPathNode,
        Prism::ConstantPathOperatorWriteNode,
        Prism::ConstantPathOrWriteNode,
        Prism::ConstantPathTargetNode,
        Prism::ConstantPathWriteNode,
      ] #: Array[singleton(Prism::Node)]

      CLASS_VARIABLE_NODES = [
        Prism::ClassVariableAndWriteNode,
        Prism::ClassVariableOperatorWriteNode,
        Prism::ClassVariableOrWriteNode,
        Prism::ClassVariableReadNode,
        Prism::ClassVariableTargetNode,
        Prism::ClassVariableWriteNode,
      ] #: Array[singleton(Prism::Node)]

      LOCAL_NODES = [
        Prism::LocalVariableAndWriteNode,
        Prism::LocalVariableOperatorWriteNode,
        Prism::LocalVariableOrWriteNode,
        Prism::LocalVariableReadNode,
        Prism::LocalVariableTargetNode,
        Prism::LocalVariableWriteNode,
        Prism::BlockParameterNode,
        Prism::RequiredParameterNode,
        Prism::RequiredKeywordParameterNode,
        Prism::OptionalKeywordParameterNode,
        Prism::RestParameterNode,
        Prism::OptionalParameterNode,
        Prism::KeywordRestParameterNode,
      ] #: Array[singleton(Prism::Node)]

      #: (ResponseBuilders::CollectionResponseBuilder[Interface::DocumentHighlight] response_builder, Prism::Node? target, Prism::Node? parent, Prism::Dispatcher dispatcher, Hash[Symbol, untyped] position) -> void
      def initialize(response_builder, target, parent, dispatcher, position)
        @response_builder = response_builder

        return unless target && parent

        highlight_target, highlight_target_value =
          case target
          when Prism::GlobalVariableReadNode, Prism::GlobalVariableAndWriteNode, Prism::GlobalVariableOperatorWriteNode,
            Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableTargetNode, Prism::GlobalVariableWriteNode,
            Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableOperatorWriteNode,
            Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode,
            Prism::InstanceVariableWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode,
            Prism::ConstantOrWriteNode, Prism::ConstantPathAndWriteNode, Prism::ConstantPathNode,
            Prism::ConstantPathOperatorWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathTargetNode,
            Prism::ConstantPathWriteNode, Prism::ConstantReadNode, Prism::ConstantTargetNode, Prism::ConstantWriteNode,
            Prism::ClassVariableAndWriteNode, Prism::ClassVariableOperatorWriteNode, Prism::ClassVariableOrWriteNode,
            Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode, Prism::ClassVariableWriteNode,
            Prism::LocalVariableAndWriteNode, Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode,
            Prism::LocalVariableReadNode, Prism::LocalVariableTargetNode, Prism::LocalVariableWriteNode,
            Prism::CallNode, Prism::BlockParameterNode, Prism::RequiredKeywordParameterNode,
            Prism::RequiredKeywordParameterNode, Prism::KeywordRestParameterNode, Prism::OptionalParameterNode,
            Prism::RequiredParameterNode, Prism::RestParameterNode
            [target, node_value(target)]
          when Prism::ModuleNode, Prism::ClassNode, Prism::SingletonClassNode, Prism::DefNode, Prism::CaseNode,
            Prism::WhileNode, Prism::UntilNode, Prism::ForNode, Prism::IfNode, Prism::UnlessNode
            [target, nil]
          end

        @target = highlight_target #: Prism::Node?
        @target_value = highlight_target_value #: String?
        @target_position = position

        if @target
          dispatcher.register(
            self,
            :on_call_node_enter,
            :on_def_node_enter,
            :on_global_variable_target_node_enter,
            :on_instance_variable_target_node_enter,
            :on_constant_path_target_node_enter,
            :on_constant_target_node_enter,
            :on_class_variable_target_node_enter,
            :on_local_variable_target_node_enter,
            :on_block_parameter_node_enter,
            :on_required_parameter_node_enter,
            :on_class_node_enter,
            :on_module_node_enter,
            :on_local_variable_read_node_enter,
            :on_constant_path_node_enter,
            :on_constant_read_node_enter,
            :on_instance_variable_read_node_enter,
            :on_class_variable_read_node_enter,
            :on_global_variable_read_node_enter,
            :on_constant_path_write_node_enter,
            :on_constant_path_or_write_node_enter,
            :on_constant_path_and_write_node_enter,
            :on_constant_path_operator_write_node_enter,
            :on_local_variable_write_node_enter,
            :on_required_keyword_parameter_node_enter,
            :on_optional_keyword_parameter_node_enter,
            :on_rest_parameter_node_enter,
            :on_optional_parameter_node_enter,
            :on_keyword_rest_parameter_node_enter,
            :on_local_variable_and_write_node_enter,
            :on_local_variable_operator_write_node_enter,
            :on_local_variable_or_write_node_enter,
            :on_class_variable_write_node_enter,
            :on_class_variable_or_write_node_enter,
            :on_class_variable_operator_write_node_enter,
            :on_class_variable_and_write_node_enter,
            :on_constant_write_node_enter,
            :on_constant_or_write_node_enter,
            :on_constant_operator_write_node_enter,
            :on_instance_variable_write_node_enter,
            :on_constant_and_write_node_enter,
            :on_instance_variable_or_write_node_enter,
            :on_instance_variable_and_write_node_enter,
            :on_instance_variable_operator_write_node_enter,
            :on_global_variable_write_node_enter,
            :on_global_variable_or_write_node_enter,
            :on_global_variable_and_write_node_enter,
            :on_global_variable_operator_write_node_enter,
            :on_singleton_class_node_enter,
            :on_case_node_enter,
            :on_while_node_enter,
            :on_until_node_enter,
            :on_for_node_enter,
            :on_if_node_enter,
            :on_unless_node_enter,
          )
        end
      end

      #: (Prism::CallNode node) -> void
      def on_call_node_enter(node)
        return unless matches?(node, [Prism::CallNode, Prism::DefNode])

        loc = node.message_loc
        # if we have `foo.` it's a call node but there is no message yet.
        return unless loc

        add_highlight(Constant::DocumentHighlightKind::READ, loc)
      end

      #: (Prism::DefNode node) -> void
      def on_def_node_enter(node)
        add_matching_end_highlights(node.def_keyword_loc, node.end_keyword_loc) if @target.is_a?(Prism::DefNode)

        return unless matches?(node, [Prism::CallNode, Prism::DefNode])

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::GlobalVariableTargetNode node) -> void
      def on_global_variable_target_node_enter(node)
        return unless matches?(node, GLOBAL_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.location)
      end

      #: (Prism::InstanceVariableTargetNode node) -> void
      def on_instance_variable_target_node_enter(node)
        return unless matches?(node, INSTANCE_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.location)
      end

      #: (Prism::ConstantPathTargetNode node) -> void
      def on_constant_path_target_node_enter(node)
        return unless matches?(node, CONSTANT_PATH_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.location)
      end

      #: (Prism::ConstantTargetNode node) -> void
      def on_constant_target_node_enter(node)
        return unless matches?(node, CONSTANT_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.location)
      end

      #: (Prism::ClassVariableTargetNode node) -> void
      def on_class_variable_target_node_enter(node)
        return unless matches?(node, CLASS_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.location)
      end

      #: (Prism::LocalVariableTargetNode node) -> void
      def on_local_variable_target_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.location)
      end

      #: (Prism::BlockParameterNode node) -> void
      def on_block_parameter_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.location)
      end

      #: (Prism::RequiredParameterNode node) -> void
      def on_required_parameter_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.location)
      end

      #: (Prism::ClassNode node) -> void
      def on_class_node_enter(node)
        add_matching_end_highlights(node.class_keyword_loc, node.end_keyword_loc) if @target.is_a?(Prism::ClassNode)

        return unless matches?(node, CONSTANT_NODES + CONSTANT_PATH_NODES + [Prism::ClassNode])

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.constant_path.location)
      end

      #: (Prism::ModuleNode node) -> void
      def on_module_node_enter(node)
        add_matching_end_highlights(node.module_keyword_loc, node.end_keyword_loc) if @target.is_a?(Prism::ModuleNode)

        return unless matches?(node, CONSTANT_NODES + CONSTANT_PATH_NODES + [Prism::ModuleNode])

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.constant_path.location)
      end

      #: (Prism::LocalVariableReadNode node) -> void
      def on_local_variable_read_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::READ, node.location)
      end

      #: (Prism::ConstantPathNode node) -> void
      def on_constant_path_node_enter(node)
        return unless matches?(node, CONSTANT_PATH_NODES)

        add_highlight(Constant::DocumentHighlightKind::READ, node.name_loc)
      end

      #: (Prism::ConstantReadNode node) -> void
      def on_constant_read_node_enter(node)
        return unless matches?(node, CONSTANT_NODES)

        add_highlight(Constant::DocumentHighlightKind::READ, node.location)
      end

      #: (Prism::InstanceVariableReadNode node) -> void
      def on_instance_variable_read_node_enter(node)
        return unless matches?(node, INSTANCE_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::READ, node.location)
      end

      #: (Prism::ClassVariableReadNode node) -> void
      def on_class_variable_read_node_enter(node)
        return unless matches?(node, CLASS_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::READ, node.location)
      end

      #: (Prism::GlobalVariableReadNode node) -> void
      def on_global_variable_read_node_enter(node)
        return unless matches?(node, GLOBAL_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::READ, node.location)
      end

      #: (Prism::ConstantPathWriteNode node) -> void
      def on_constant_path_write_node_enter(node)
        return unless matches?(node, CONSTANT_PATH_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.target.location)
      end

      #: (Prism::ConstantPathOrWriteNode node) -> void
      def on_constant_path_or_write_node_enter(node)
        return unless matches?(node, CONSTANT_PATH_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.target.location)
      end

      #: (Prism::ConstantPathAndWriteNode node) -> void
      def on_constant_path_and_write_node_enter(node)
        return unless matches?(node, CONSTANT_PATH_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.target.location)
      end

      #: (Prism::ConstantPathOperatorWriteNode node) -> void
      def on_constant_path_operator_write_node_enter(node)
        return unless matches?(node, CONSTANT_PATH_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.target.location)
      end

      #: (Prism::LocalVariableWriteNode node) -> void
      def on_local_variable_write_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::RequiredKeywordParameterNode node) -> void
      def on_required_keyword_parameter_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::OptionalKeywordParameterNode node) -> void
      def on_optional_keyword_parameter_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::RestParameterNode node) -> void
      def on_rest_parameter_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        name_loc = node.name_loc
        add_highlight(Constant::DocumentHighlightKind::WRITE, name_loc) if name_loc
      end

      #: (Prism::OptionalParameterNode node) -> void
      def on_optional_parameter_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::KeywordRestParameterNode node) -> void
      def on_keyword_rest_parameter_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        name_loc = node.name_loc
        add_highlight(Constant::DocumentHighlightKind::WRITE, name_loc) if name_loc
      end

      #: (Prism::LocalVariableAndWriteNode node) -> void
      def on_local_variable_and_write_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::LocalVariableOperatorWriteNode node) -> void
      def on_local_variable_operator_write_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::LocalVariableOrWriteNode node) -> void
      def on_local_variable_or_write_node_enter(node)
        return unless matches?(node, LOCAL_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::ClassVariableWriteNode node) -> void
      def on_class_variable_write_node_enter(node)
        return unless matches?(node, CLASS_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::ClassVariableOrWriteNode node) -> void
      def on_class_variable_or_write_node_enter(node)
        return unless matches?(node, CLASS_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::ClassVariableOperatorWriteNode node) -> void
      def on_class_variable_operator_write_node_enter(node)
        return unless matches?(node, CLASS_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::ClassVariableAndWriteNode node) -> void
      def on_class_variable_and_write_node_enter(node)
        return unless matches?(node, CLASS_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::ConstantWriteNode node) -> void
      def on_constant_write_node_enter(node)
        return unless matches?(node, CONSTANT_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::ConstantOrWriteNode node) -> void
      def on_constant_or_write_node_enter(node)
        return unless matches?(node, CONSTANT_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::ConstantOperatorWriteNode node) -> void
      def on_constant_operator_write_node_enter(node)
        return unless matches?(node, CONSTANT_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::InstanceVariableWriteNode node) -> void
      def on_instance_variable_write_node_enter(node)
        return unless matches?(node, INSTANCE_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::InstanceVariableOrWriteNode node) -> void
      def on_instance_variable_or_write_node_enter(node)
        return unless matches?(node, INSTANCE_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::InstanceVariableAndWriteNode node) -> void
      def on_instance_variable_and_write_node_enter(node)
        return unless matches?(node, INSTANCE_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::InstanceVariableOperatorWriteNode node) -> void
      def on_instance_variable_operator_write_node_enter(node)
        return unless matches?(node, INSTANCE_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::ConstantAndWriteNode node) -> void
      def on_constant_and_write_node_enter(node)
        return unless matches?(node, CONSTANT_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::GlobalVariableWriteNode node) -> void
      def on_global_variable_write_node_enter(node)
        return unless matches?(node, GLOBAL_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::GlobalVariableOrWriteNode node) -> void
      def on_global_variable_or_write_node_enter(node)
        return unless matches?(node, GLOBAL_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::GlobalVariableAndWriteNode node) -> void
      def on_global_variable_and_write_node_enter(node)
        return unless matches?(node, GLOBAL_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::GlobalVariableOperatorWriteNode node) -> void
      def on_global_variable_operator_write_node_enter(node)
        return unless matches?(node, GLOBAL_VARIABLE_NODES)

        add_highlight(Constant::DocumentHighlightKind::WRITE, node.name_loc)
      end

      #: (Prism::SingletonClassNode node) -> void
      def on_singleton_class_node_enter(node)
        return unless @target.is_a?(Prism::SingletonClassNode)

        add_matching_end_highlights(node.class_keyword_loc, node.end_keyword_loc)
      end

      #: (Prism::CaseNode node) -> void
      def on_case_node_enter(node)
        return unless @target.is_a?(Prism::CaseNode)

        add_matching_end_highlights(node.case_keyword_loc, node.end_keyword_loc)
      end

      #: (Prism::WhileNode node) -> void
      def on_while_node_enter(node)
        return unless @target.is_a?(Prism::WhileNode)

        add_matching_end_highlights(node.keyword_loc, node.closing_loc)
      end

      #: (Prism::UntilNode node) -> void
      def on_until_node_enter(node)
        return unless @target.is_a?(Prism::UntilNode)

        add_matching_end_highlights(node.keyword_loc, node.closing_loc)
      end

      #: (Prism::ForNode node) -> void
      def on_for_node_enter(node)
        return unless @target.is_a?(Prism::ForNode)

        add_matching_end_highlights(node.for_keyword_loc, node.end_keyword_loc)
      end

      #: (Prism::IfNode node) -> void
      def on_if_node_enter(node)
        return unless @target.is_a?(Prism::IfNode)

        add_matching_end_highlights(node.if_keyword_loc, node.end_keyword_loc)
      end

      #: (Prism::UnlessNode node) -> void
      def on_unless_node_enter(node)
        return unless @target.is_a?(Prism::UnlessNode)

        add_matching_end_highlights(node.keyword_loc, node.end_keyword_loc)
      end

      private

      #: (Prism::Node node, Array[singleton(Prism::Node)] classes) -> bool?
      def matches?(node, classes)
        classes.any? { |klass| @target.is_a?(klass) } && @target_value == node_value(node)
      end

      #: (Integer kind, Prism::Location location) -> void
      def add_highlight(kind, location)
        @response_builder << Interface::DocumentHighlight.new(range: range_from_location(location), kind: kind)
      end

      #: (Prism::Node? node) -> String?
      def node_value(node)
        case node
        when Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::BlockArgumentNode, Prism::ConstantTargetNode,
          Prism::ConstantPathWriteNode, Prism::ConstantPathTargetNode, Prism::ConstantPathOrWriteNode,
          Prism::ConstantPathOperatorWriteNode, Prism::ConstantPathAndWriteNode
          node.slice
        when Prism::GlobalVariableReadNode, Prism::GlobalVariableAndWriteNode, Prism::GlobalVariableOperatorWriteNode,
          Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableTargetNode, Prism::GlobalVariableWriteNode,
          Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableOperatorWriteNode,
          Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode,
          Prism::InstanceVariableWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode,
          Prism::ConstantOrWriteNode, Prism::ConstantWriteNode, Prism::ClassVariableAndWriteNode,
          Prism::ClassVariableOperatorWriteNode, Prism::ClassVariableOrWriteNode, Prism::ClassVariableReadNode,
          Prism::ClassVariableTargetNode, Prism::ClassVariableWriteNode, Prism::LocalVariableAndWriteNode,
          Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode, Prism::LocalVariableReadNode,
          Prism::LocalVariableTargetNode, Prism::LocalVariableWriteNode, Prism::DefNode, Prism::BlockParameterNode,
          Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode, Prism::KeywordRestParameterNode,
          Prism::OptionalParameterNode, Prism::RequiredParameterNode, Prism::RestParameterNode

          node.name.to_s
        when Prism::CallNode
          node.message
        when Prism::ClassNode, Prism::ModuleNode
          node.constant_path.slice
        end
      end

      #: (Prism::Location? keyword_loc, Prism::Location? end_loc) -> void
      def add_matching_end_highlights(keyword_loc, end_loc)
        return unless keyword_loc && end_loc
        return unless end_loc.length.positive?
        return unless covers_target_position?(keyword_loc) || covers_target_position?(end_loc)

        add_highlight(Constant::DocumentHighlightKind::TEXT, keyword_loc)
        add_highlight(Constant::DocumentHighlightKind::TEXT, end_loc)
      end

      #: (Prism::Location location) -> bool
      def covers_target_position?(location)
        start_line = location.start_line - 1
        end_line = location.end_line - 1
        start_covered = start_line < @target_position[:line] ||
          (start_line == @target_position[:line] && location.start_column <= @target_position[:character])
        end_covered = end_line > @target_position[:line] ||
          (end_line == @target_position[:line] && location.end_column >= @target_position[:character])
        start_covered && end_covered
      end
    end
  end
end