lib/rubocop/file_inspector.rb



# encoding: utf-8

module Rubocop
  # This class handles the processing of files, which includes dealing with
  # formatters and letting cops inspect the files.
  class FileInspector
    def initialize(options)
      @options = options
      @errors = []
    end

    # Takes a block which it calls once per inspected file.  The block shall
    # return true if the caller wants to break the loop early.
    def process_files(target_files, config_store)
      target_files.each(&:freeze).freeze
      inspected_files = []
      any_failed = false

      formatter_set.started(target_files)

      target_files.each do |file|
        break if yield
        offenses = process_file(file, config_store)

        any_failed = true unless offenses.empty?
        inspected_files << file
      end

      formatter_set.finished(inspected_files.freeze)
      formatter_set.close_output_files
      any_failed
    end

    def display_error_summary
      return if @errors.empty?
      plural = @errors.count > 1 ? 's' : ''
      warn "\n#{@errors.count} error#{plural} occurred:".color(:red)
      @errors.each { |error| warn error }
      warn 'Errors are usually caused by RuboCop bugs.'
      warn 'Please, report your problems to RuboCop\'s issue tracker.'
      warn 'Mention the following information in the issue report:'
      warn Rubocop::Version.version(true)
    end

    private

    def process_file(file, config_store)
      puts "Scanning #{file}" if @options[:debug]
      offenses = []
      formatter_set.file_started(file, {})

      # When running with --auto-correct, we need to inspect the file (which
      # includes writing a corrected version of it) until no more corrections
      # are made. This is because automatic corrections can introduce new
      # offenses. In the normal case the loop is only executed once.
      loop do
        new_offenses, updated_source_file = inspect_file(file, config_store)
        unique_new = new_offenses.reject { |n| offenses.include?(n) }
        offenses += unique_new
        break unless updated_source_file
      end

      formatter_set.file_finished(file, offenses.sort.freeze)
      offenses
    end

    def inspect_file(file, config_store)
      config = config_store.for(file)
      team = Cop::Team.new(mobilized_cop_classes(config), config, @options)
      offenses = team.inspect_file(file)
      @errors.concat(team.errors)
      [offenses, team.updated_source_file?]
    end

    def mobilized_cop_classes(config)
      @mobilized_cop_classes ||= {}
      @mobilized_cop_classes[config.object_id] ||= begin
        cop_classes = Cop::Cop.all

        if @options[:only]
          cop_classes.select! { |c| c.cop_name == @options[:only] }
        else
          # filter out Rails cops unless requested
          cop_classes.reject!(&:rails?) unless run_rails_cops?(config)

          # filter out style cops when --lint is passed
          cop_classes.select!(&:lint?) if @options[:lint]
        end

        cop_classes
      end
    end

    def run_rails_cops?(config)
      @options[:rails] || config['AllCops']['RunRailsCops']
    end

    def formatter_set
      @formatter_set ||= begin
        set = Formatter::FormatterSet.new
        pairs = @options[:formatters] || [[Options::DEFAULT_FORMATTER]]
        pairs.each do |formatter_key, output_path|
          set.add_formatter(formatter_key, output_path)
        end
        set
      rescue => error
        warn error.message
        $stderr.puts error.backtrace
        exit(1)
      end
    end
  end
end