lib/rubocop/options.rb



# encoding: utf-8

require 'optparse'

module Rubocop
  # This class handles command line options.
  class Options
    DEFAULT_FORMATTER = 'progress'

    def initialize(config_store)
      @config_store = config_store
      @options = {}
    end

    # rubocop:disable MethodLength
    def parse(args)
      ignore_dropped_options(args)
      convert_deprecated_options(args)

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

        opts.on('-d', '--debug', 'Display debug info.') do |d|
          @options[:debug] = d
        end
        opts.on('-c', '--config FILE', 'Specify configuration file.') do |f|
          @options[:config] = f
          @config_store.set_options_config(@options[:config])
        end
        opts.on('--only COP', 'Run just one cop.') do |s|
          @options[:only] = s
          validate_only_option
        end
        opts.on('--auto-gen-config',
                'Generate a configuration file acting as a',
                'TODO list.') do
          @options[:auto_gen_config] = true
          @options[:formatters] = [
                                   [DEFAULT_FORMATTER],
                                   [Formatter::DisabledConfigFormatter,
                                    ConfigLoader::AUTO_GENERATED_FILE]
                                  ]
          validate_auto_gen_config_option(args)
        end
        opts.on('--show-cops',
                'Shows cops and their config for the',
                'current directory.') do
          print_available_cops
          exit(0)
        end
        opts.on('-f', '--format FORMATTER',
                'Choose an output formatter. This option',
                'can be specified multiple times to enable',
                'multiple formatters at the same time.',
                '  [p]rogress (default)',
                '  [s]imple',
                '  [c]lang',
                '  [e]macs',
                '  [j]son',
                '  [f]iles',
                '  [o]ffences',
                '  custom formatter class name') do |key|
          @options[:formatters] ||= []
          @options[:formatters] << [key]
        end
        opts.on('-o', '--out FILE',
                'Write output to a file instead of STDOUT.',
                'This option applies to the previously',
                'specified --format, or the default format',
                'if no format is specified.') do |path|
          @options[:formatters] ||= [[DEFAULT_FORMATTER]]
          @options[:formatters].last << path
        end
        opts.on('-r', '--require FILE', 'Require Ruby file.') do |f|
          require f
        end
        opts.on('-R', '--rails', 'Run extra Rails cops.') do |r|
          @options[:rails] = r
        end
        opts.on('-l', '--lint', 'Run only lint cops.') do |l|
          @options[:lint] = l
        end
        opts.on('-a', '--auto-correct', 'Auto-correct offences.') do |a|
          @options[:autocorrect] = a
        end
        opts.on('-n', '--no-color', 'Disable color output.') do |s|
          Sickill::Rainbow.enabled = false
        end
        opts.on('-v', '--version', 'Display version.') do
          puts Rubocop::Version.version(false)
          exit(0)
        end
        opts.on('-V', '--verbose-version', 'Display verbose version.') do
          puts Rubocop::Version.version(true)
          exit(0)
        end
      end.parse!(args)

      target_files = target_finder.find(args)
      [@options, target_files]
    end
    # rubocop:enable MethodLength

    private

    def ignore_dropped_options(args)
      # Currently we don't make -s/--silent option raise error
      # since those are mostly used by external tools.
      rejected = args.reject! { |a| %w(-s --silent).include?(a) }
      if rejected
        warn '-s/--silent options is dropped. ' +
          '`emacs` and `files` formatters no longer display summary.'
      end
    end

    def convert_deprecated_options(args)
      args.map! do |arg|
        case arg
        when '-e', '--emacs'
          deprecate("#{arg} option", '--format emacs', '1.0.0')
          %w(--format emacs)
        else
          arg
        end
      end.flatten!
    end

    def deprecate(subject, alternative = nil, version = nil)
      message =  "#{subject} is deprecated"
      message << " and will be removed in RuboCop #{version}" if version
      message << '.'
      message << " Please use #{alternative} instead." if alternative
      warn message
    end

    def validate_only_option
      if Cop::Cop.all.none? { |c| c.cop_name == @options[:only] }
        fail ArgumentError, "Unrecognized cop name: #{@options[:only]}."
      end
    end

    def validate_auto_gen_config_option(args)
      if args.any?
        fail ArgumentError,
             '--auto-gen-config can not be combined with any other arguments.'
      end

      target_finder.find(args).each do |file|
        config = @config_store.for(file)
        if @options[:auto_gen_config] && config.contains_auto_generated_config
          fail "Remove #{ConfigLoader::AUTO_GENERATED_FILE} from the " +
            'current configuration before generating it again.'
        end
      end
    end

    def target_finder
      @target_finder ||= TargetFinder.new(@config_store, @options[:debug])
    end

    def print_available_cops
      cops = Cop::Cop.all
      puts "Available cops (#{cops.length}) + config for #{Dir.pwd.to_s}: "
      dirconf = @config_store.for(Dir.pwd.to_s)
      cops.types.sort!.each do |type|
        coptypes = cops.with_type(type).sort_by!(&:cop_name)
        puts "Type '#{type.to_s.capitalize}' (#{coptypes.size}):"
        coptypes.each do |cop|
          puts " - #{cop.cop_name}"
          cnf = dirconf.for_cop(cop).dup
          print_conf_option('Description',
                            cnf.delete('Description') { 'None' })
          cnf.each { |k, v| print_conf_option(k, v) }
          print_conf_option('SupportsAutoCorrection',
                            cop.new.support_autocorrect?.to_s)
        end
      end
    end

    def print_conf_option(option, value)
      puts  "    - #{option}: #{value}"
    end
  end
end