lib/rubocop/cop/team.rb



# frozen_string_literal: true

module RuboCop
  module Cop
    # FIXME
    class Team
      DEFAULT_OPTIONS = {
        auto_correct: false,
        debug: false
      }.freeze

      Investigation = Struct.new(:offenses, :errors)

      attr_reader :errors, :warnings, :updated_source_file

      alias updated_source_file? updated_source_file

      def initialize(cop_classes, config, options = nil)
        @cop_classes = cop_classes
        @config = config
        @options = options || DEFAULT_OPTIONS
        @errors = []
        @warnings = []

        validate_config
      end

      def autocorrect?
        @options[:auto_correct]
      end

      def debug?
        @options[:debug]
      end

      def inspect_file(processed_source)
        # If we got any syntax errors, return only the syntax offenses.
        unless processed_source.valid_syntax?
          return Lint::Syntax.offenses_from_processed_source(
            processed_source, @config, @options
          )
        end

        offenses(processed_source)
      end

      def cops
        only = @options.fetch(:only, [])
        safe = @options.fetch(:safe, false)
        @cops ||= @cop_classes.enabled(@config, only, safe).map do |cop_class|
          cop_class.new(@config, @options)
        end
      end

      def forces
        @forces ||= forces_for(cops)
      end

      def forces_for(cops)
        Force.all.each_with_object([]) do |force_class, forces|
          joining_cops = cops.select { |cop| cop.join_force?(force_class) }
          next if joining_cops.empty?

          forces << force_class.new(joining_cops)
        end
      end

      def autocorrect(buffer, cops)
        @updated_source_file = false
        return unless autocorrect?

        new_source = autocorrect_all_cops(buffer, cops)

        return if new_source == buffer.source

        if @options[:stdin]
          # holds source read in from stdin, when --stdin option is used
          @options[:stdin] = new_source
        else
          filename = buffer.name
          File.open(filename, 'w') { |f| f.write(new_source) }
        end
        @updated_source_file = true
      end

      private

      def offenses(processed_source)
        # The autocorrection process may have to be repeated multiple times
        # until there are no corrections left to perform
        # To speed things up, run auto-correcting cops by themselves, and only
        # run the other cops when no corrections are left
        autocorrect_cops, other_cops = cops.partition(&:autocorrect?)

        autocorrect =
          investigate(autocorrect_cops, processed_source) do |offenses|
            # We corrected some errors. Another round of inspection will be
            # done, and any other offenses will be caught then, so we don't
            # need to continue.
            return offenses if autocorrect(processed_source.buffer,
                                           autocorrect_cops)
          end

        other = investigate(other_cops, processed_source)

        errors = autocorrect.errors.merge(other.errors)
        process_commissioner_errors(processed_source.path, errors)

        autocorrect.offenses.concat(other.offenses)
      end

      def investigate(cops, processed_source)
        return Investigation.new([], {}) if cops.empty?

        commissioner = Commissioner.new(cops, forces_for(cops))
        offenses = commissioner.investigate(processed_source)
        yield offenses if block_given?

        Investigation.new(offenses, commissioner.errors)
      end

      def autocorrect_all_cops(buffer, cops)
        corrector = Corrector.new(buffer)

        collate_corrections(corrector, cops)

        if !corrector.corrections.empty?
          corrector.rewrite
        else
          buffer.source
        end
      end

      def collate_corrections(corrector, cops)
        skips = Set.new

        cops.each do |cop|
          next if cop.corrections.empty?
          next if skips.include?(cop.class)

          corrector.corrections.concat(cop.corrections)
          skips.merge(cop.class.autocorrect_incompatible_with)
        end
      end

      def validate_config
        cops.each do |cop|
          cop.validate_config if cop.respond_to?(:validate_config)
        end
      end

      def process_commissioner_errors(file, file_errors)
        file_errors.each do |cop, errors|
          errors.each do |cop_error|
            e = cop_error.error
            line = ":#{cop_error.line}" if cop_error.line
            column = ":#{cop_error.column}" if cop_error.column
            location = "#{file}#{line}#{column}"

            if e.is_a?(Warning)
              handle_warning(e, location)
            else
              handle_error(e, location, cop)
            end
          end
        end
      end

      def handle_warning(error, location)
        message = Rainbow("#{error.message} (from file: #{location})").yellow

        @warnings << message
        warn message
        puts error.backtrace if debug?
      end

      def handle_error(error, location, cop)
        message = Rainbow("An error occurred while #{cop.name}" \
                           " cop was inspecting #{location}.").red
        @errors << message
        warn message
        if debug?
          puts error.message, error.backtrace
        else
          warn 'To see the complete backtrace run rubocop -d.'
        end
      end
    end
  end
end