lib/rubocop/cli.rb



# encoding: utf-8

require 'optparse'

module Rubocop
  # The CLI is a class responsible of handling all the command line interface
  # logic.
  class CLI
    # Entry point for the application logic. Here we
    # do the command line arguments processing and inspect
    # the target files
    # @return [Fixnum] UNIX exit code
    def run(args = ARGV)
      $options = { mode: :default }

      OptionParser.new do |opts|
        opts.banner = 'Usage: rubocop [options] [file1, file2, ...]'

        opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
          $options[:verbose] = v
        end
        opts.on('-e', '--emacs', 'Emacs style output') do
          $options[:mode] = :emacs_style
        end
      end.parse!(args)

      cops = Cop::Cop.all
      show_cops_on_duty(cops) if $options[:verbose]
      total_offences = 0

      target_files(args).each do |file|
        report = Report.create(file, $options[:mode])
        source = File.readlines(file).map do |line|
          get_rid_of_invalid_byte_sequences(line)
          line.chomp
        end

        tokens, sexp, correlations = CLI.rip_source(source)

        cops.each do |cop_klass|
          cop = cop_klass.new
          cop.correlations = correlations
          cop.inspect(file, source, tokens, sexp)
          total_offences += cop.offences.count
          report << cop if cop.has_report?
        end

        report.display unless report.empty?
      end

      print "\n#{target_files(args).count} files inspected, "
      puts "#{total_offences} offences detected"

      return total_offences == 0 ? 0 : 1
    end

    def get_rid_of_invalid_byte_sequences(line)
      enc = line.encoding.name
      # UTF-16 works better in this algorithm but is not supported in 1.9.2.
      temporary_encoding = (RUBY_VERSION == '1.9.2') ? 'UTF-8' : 'UTF-16'
      line.encode!(temporary_encoding, enc, invalid: :replace, replace: '')
      line.encode!(enc, temporary_encoding)
    end

    def self.rip_source(source)
      tokens = Ripper.lex(source.join("\n")).map { |t| Cop::Token.new(*t) }
      sexp = Ripper.sexp(source.join("\n"))
      Cop::Position.make_position_objects(sexp)
      correlations = Cop::Grammar.new(tokens).correlate(sexp)
      [tokens, sexp, correlations]
    end

    def show_cops_on_duty(cops)
      puts 'Reporting for duty:'
      cops.each { |c| puts c }
      puts '*******************'
    end

    # Generate a list of target files by expanding globing patterns
    # (if any). If args is empty recursively finds all Ruby source
    # files in the current directory
    # @return [Array] array of filenames
    def target_files(args)
      return Dir['**/*.rb'] if args.empty?

      files = []

      args.each do |target|
        if File.directory?(target)
          files << Dir["#{target}/**/*.rb"]
        elsif target =~ /\*/
          files << Dir[target]
        else
          files << target
        end
      end

      files.flatten
    end
  end
end