lib/solargraph/diagnostics/rubocop.rb
require 'open3' require 'shellwords' module Solargraph module Diagnostics # This reporter provides linting through RuboCop. # class Rubocop < Base # Conversion of RuboCop severity names to LSP constants SEVERITIES = { 'refactor' => Severities::HINT, 'convention' => Severities::INFORMATION, 'warning' => Severities::WARNING, 'error' => Severities::ERROR, 'fatal' => Severities::ERROR } # The rubocop command # # @return [String] attr_reader :command def initialize(command = 'rubocop') @command = command end # @param source [Solargraph::Source] # @param api_map [Solargraph::ApiMap] # @return [Array<Hash>] def diagnose source, api_map begin text = source.code filename = source.filename raise DiagnosticsError, 'No command specified' if command.nil? or command.empty? cmd = "#{Shellwords.escape(command)} -f j" unless api_map.workspace.nil? or api_map.workspace.directory.nil? rc = File.join(api_map.workspace.directory, '.rubocop.yml') cmd += " -c #{Shellwords.escape(fix_drive_letter(rc))}" if File.file?(rc) end cmd += " -s #{Shellwords.escape(fix_drive_letter(filename))}" o, e, s = Open3.capture3(cmd, stdin_data: text) STDERR.puts e unless e.empty? raise DiagnosticsError, "Command '#{command}' is not available (gem exception)" if e.include?('Gem::Exception') raise DiagnosticsError, "RuboCop returned empty data" if o.empty? make_array JSON.parse(o) rescue JSON::ParserError raise DiagnosticsError, 'RuboCop returned invalid data' rescue Errno::ENOENT raise DiagnosticsError, "Command '#{command}' is not available" end end private def make_array resp diagnostics = [] resp['files'].each do |file| file['offenses'].each do |off| if off['location']['start_line'] != off['location']['last_line'] last_line = off['location']['start_line'] last_column = 0 else last_line = off['location']['last_line'] - 1 last_column = off['location']['last_column'] end diag = { range: { start: { line: off['location']['start_line'] - 1, character: off['location']['start_column'] - 1 }, end: { line: last_line, character: last_column } }, # 1 = Error, 2 = Warning, 3 = Information, 4 = Hint severity: SEVERITIES[off['severity']], source: off['cop_name'], message: off['message'].gsub(/^#{off['cop_name']}\:/, '') } diagnostics.push diag end end diagnostics end # RuboCop internally uses capitalized drive letters for Windows paths, # so we need to convert the paths provided to the command. # # @param path [String] # @return [String] def fix_drive_letter path return path unless path.match(/^[a-z]:/) path[0].upcase + path[1..-1] end end end end