lib/ruby_lsp/requests/diagnostics.rb



# typed: strict
# frozen_string_literal: true

module RubyLsp
  module Requests
    # The
    # [diagnostics](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics)
    # request informs the editor of RuboCop offenses for a given file.
    class Diagnostics < Request
      class << self
        #: -> Interface::DiagnosticRegistrationOptions
        def provider
          Interface::DiagnosticRegistrationOptions.new(
            document_selector: nil,
            inter_file_dependencies: false,
            workspace_diagnostics: false,
          )
        end
      end

      #: (GlobalState global_state, RubyDocument document) -> void
      def initialize(global_state, document)
        super()
        @active_linters = global_state.active_linters #: Array[Support::Formatter]
        @document = document
        @uri = document.uri #: URI::Generic
      end

      # @override
      #: -> (Array[Interface::Diagnostic] & Object)?
      def perform
        diagnostics = []
        diagnostics.concat(syntax_error_diagnostics, syntax_warning_diagnostics)

        # Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
        if @document.syntax_error? || @active_linters.empty? || @document.past_expensive_limit?
          return diagnostics
        end

        @active_linters.each do |linter|
          linter_diagnostics = linter.run_diagnostic(@uri, @document)
          diagnostics.concat(linter_diagnostics) if linter_diagnostics
        end

        diagnostics
      end

      private

      #: -> Array[Interface::Diagnostic]
      def syntax_warning_diagnostics
        @document.parse_result.warnings.map do |warning|
          location = warning.location

          Interface::Diagnostic.new(
            source: "Prism",
            message: warning.message,
            severity: Constant::DiagnosticSeverity::WARNING,
            range: Interface::Range.new(
              start: Interface::Position.new(
                line: location.start_line - 1,
                character: location.start_column,
              ),
              end: Interface::Position.new(
                line: location.end_line - 1,
                character: location.end_column,
              ),
            ),
          )
        end
      end

      #: -> Array[Interface::Diagnostic]
      def syntax_error_diagnostics
        @document.parse_result.errors.map do |error|
          location = error.location

          Interface::Diagnostic.new(
            range: Interface::Range.new(
              start: Interface::Position.new(
                line: location.start_line - 1,
                character: location.start_column,
              ),
              end: Interface::Position.new(
                line: location.end_line - 1,
                character: location.end_column,
              ),
            ),
            message: error.message,
            severity: Constant::DiagnosticSeverity::ERROR,
            source: "Prism",
          )
        end
      end
    end
  end
end