class Rubocop::CLI

The CLI is a class responsible of handling all the command line interface logic.

Constants

DEFAULT_FORMATTER

Attributes

options[RW]
wants_to_quit[RW]

If set true while running, RuboCop will abort processing and exit gracefully.

wants_to_quit?[RW]

If set true while running, RuboCop will abort processing and exit gracefully.

Public Class Methods

new() click to toggle source
# File lib/rubocop/cli.rb, line 18
def initialize
  @cops = Cop::Cop.all
  @errors = []
  @options = {}
  ConfigStore.prepare
end
parse(file) { |source_buffer| ... } click to toggle source
# File lib/rubocop/cli.rb, line 277
def self.parse(file)
  parser = Parser::CurrentRuby.new

  # On JRuby and Rubinius, there's a risk that we hang in
  # tokenize() if we don't set the all errors as fatal flag.
  parser.diagnostics.all_errors_are_fatal = RUBY_ENGINE != 'ruby'
  parser.diagnostics.ignore_warnings      = false

  diagnostics = []
  parser.diagnostics.consumer = lambda do |diagnostic|
    diagnostics << diagnostic
  end

  source_buffer = Parser::Source::Buffer.new(file, 1)
  yield source_buffer

  begin
    ast, comments, tokens = parser.tokenize(source_buffer)
  rescue Parser::SyntaxError # rubocop:disable HandleExceptions
    # All errors are in diagnostics. No need to handle exception.
  end

  if tokens
    tokens = tokens.map do |t|
      type, details = *t
      text, range = *details
      Rubocop::Cop::Token.new(range, type, text)
    end
  end

  syntax_offences = diagnostics.map do |d|
    Cop::Offence.new(d.level, d.location, "#{d.message}",
                     'Syntax')
  end

  source = source_buffer.source.split($RS)

  [ast, comments, tokens, source_buffer, source, syntax_offences]
end

Public Instance Methods

convert_deprecated_options!(args) click to toggle source

rubocop:enable MethodLength

# File lib/rubocop/cli.rb, line 214
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
disabled_lines_in(source) click to toggle source
# File lib/rubocop/cli.rb, line 246
def disabled_lines_in(source)
  disabled_lines = Hash.new([])
  disabled_section = {}
  regexp = '# rubocop : (%s)\b ((?:\w+,? )+)'.gsub(' ', '\s*')
  section_regexp = '^\s*' + sprintf(regexp, '(?:dis|en)able')
  single_line_regexp = '\S.*' + sprintf(regexp, 'disable')

  source.each_with_index do |line, ix|
    each_mentioned_cop(/#{section_regexp}/, line) do |cop_name, kind|
      disabled_section[cop_name] = (kind == 'disable')
    end
    disabled_section.keys.each do |cop_name|
      disabled_lines[cop_name] += [ix + 1] if disabled_section[cop_name]
    end

    each_mentioned_cop(/#{single_line_regexp}/, line) do |cop_name, kind|
      disabled_lines[cop_name] += [ix + 1] if kind == 'disable'
    end
  end
  disabled_lines
end
display_error_summary(errors) click to toggle source
# File lib/rubocop/cli.rb, line 235
def display_error_summary(errors)
  return if errors.empty?
  plural = errors.count > 1 ? 's' : ''
  puts "\n#{errors.count} error#{plural} occurred:".color(:red)
  errors.each { |error| puts error }
  puts 'Errors are usually caused by RuboCop bugs.'
  puts 'Please, report your problems to RuboCop\'s issue tracker.'
  puts 'Mention the following information in the issue report:'
  puts Rubocop::Version.version(true)
end
each_mentioned_cop(regexp, line) { |cop_name, kind| ... } click to toggle source
# File lib/rubocop/cli.rb, line 268
def each_mentioned_cop(regexp, line)
  match = line.match(regexp)
  if match
    kind, cops = match.captures
    cops = Cop::Cop.all.map(&:cop_name).join(',') if cops.include?('all')
    cops.split(/,\s*/).each { |cop_name| yield cop_name, kind }
  end
end
handle_error(e, message) click to toggle source
# File lib/rubocop/cli.rb, line 132
def handle_error(e, message)
  @errors << message
  warn message
  if @options[:debug]
    puts e.message, e.backtrace
  else
    warn 'To see the complete backtrace run rubocop -d.'
  end
end
inspect_file(file) click to toggle source
# File lib/rubocop/cli.rb, line 79
def inspect_file(file)
  begin
    ast, comments, tokens, source_buffer, source, syntax_offences =
      CLI.parse(file) { |sb| sb.read }

  rescue Encoding::UndefinedConversionError, ArgumentError => e
    handle_error(e, "An error occurred while parsing #{file}.".color(:red))
    return []
  end

  # If we got any syntax errors, return only the syntax offences.
  # Parser may return nil for AST even though there are no syntax errors.
  # e.g. sources which contain only comments
  return syntax_offences unless syntax_offences.empty?

  config = ConfigStore.for(file)
  disabled_lines = disabled_lines_in(source)

  set_config_for_all_cops(config)

  @cops.reduce([]) do |offences, cop_class|
    cop_name = cop_class.cop_name
    if config.cop_enabled?(cop_name)
      cop = setup_cop(cop_class, disabled_lines)
      if !@options[:only] || @options[:only] == cop_name
        begin
          cop.inspect(source_buffer, source, tokens, ast, comments)
        rescue => e
          handle_error(e,
                       "An error occurred while #{cop.name}".color(:red) +
                       " cop was inspecting #{file}.".color(:red))
        end
      end
      offences.concat(cop.offences)
    end
    offences
  end.sort
end
parse_options(args) click to toggle source

rubocop:disable MethodLength

# File lib/rubocop/cli.rb, line 143
def parse_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('-L', '--list-cops', 'List class name of cops.') do |c|
      @options[:list_cops] = c
    end
    opts.on('-c', '--config FILE', 'Specify configuration file.') do |f|
      @options[:config] = f
      ConfigStore.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('-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',
            '  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('-s', '--silent', 'Silence summary.') do |s|
      @options[:silent] = s
    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)
end
ruby_files(root = Dir.pwd) click to toggle source

Finds all Ruby source files under the current or other supplied directory. A Ruby source file is defined as a file with the ‘.rb` extension or a file with no extension that has a ruby shebang line as its first line. It is possible to specify includes and excludes using the config file, so you can include other Ruby files like Rakefiles and gemspecs. @param root Root directory under which to search for ruby source files @return [Array] Array of filenames

# File lib/rubocop/cli.rb, line 347
def ruby_files(root = Dir.pwd)
  files = Dir["#{root}/**/*"].select { |file| FileTest.file?(file) }

  rb = []

  rb += files.select { |file| File.extname(file) == '.rb' }
  rb += files.select do |file|
    if File.extname(file) == '' && !excluded_file?(file)
      begin
        File.open(file) { |f| f.readline } =~ /#!.*ruby/
      rescue EOFError, ArgumentError => e
        log_error(e, "Unprocessable file #{file.inspect}: ")
        false
      end
    end
  end

  rb += files.select do |file|
    config = ConfigStore.for(file)
    config.file_to_include?(file)
  end

  rb.reject { |file| excluded_file?(file) }.uniq
end
run(args = ARGV) click to toggle source

Entry point for the application logic. Here we do the command line arguments processing and inspect the target files @return [Fixnum] UNIX exit code

# File lib/rubocop/cli.rb, line 29
def run(args = ARGV)
  trap_interrupt

  begin
    parse_options(args)
  rescue => e
    $stderr.puts e.message
    return 1
  end

  # filter out Rails cops unless requested
  @cops.reject!(&:rails?) unless @options[:rails]

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

  target_files = target_files(args)
  target_files.each(&:freeze).freeze
  inspected_files = []
  any_failed = false

  formatter_set.started(target_files)

  target_files.each do |file|
    break if wants_to_quit?

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

    offences = inspect_file(file)

    any_failed = true unless offences.empty?
    inspected_files << file
    formatter_set.file_finished(file, offences.freeze)
  end

  formatter_set.finished(inspected_files.freeze)
  formatter_set.close_output_files

  display_error_summary(@errors) unless @options[:silent]

  !any_failed && !wants_to_quit ? 0 : 1
end
set_config_for_all_cops(config) click to toggle source
# File lib/rubocop/cli.rb, line 118
def set_config_for_all_cops(config)
  @cops.each do |cop_class|
    cop_class.config = config.for_cop(cop_class.cop_name)
  end
end
setup_cop(cop_class, disabled_lines = nil) click to toggle source
# File lib/rubocop/cli.rb, line 124
def setup_cop(cop_class, disabled_lines = nil)
  cop = cop_class.new
  cop.debug = @options[:debug] || @options[:list_cops]
  cop.autocorrect = @options[:autocorrect]
  cop.disabled_lines = disabled_lines[cop_class.cop_name] if disabled_lines
  cop
end
target_files(args) click to toggle source

Generate a list of target files by expanding globing patterns (if any). If args is empty recursively finds all Ruby source files under the current directory @return [Array] array of filenames

# File lib/rubocop/cli.rb, line 321
def target_files(args)
  return ruby_files if args.empty?

  files = []

  args.each do |target|
    if File.directory?(target)
      files += ruby_files(target.chomp(File::SEPARATOR))
    elsif target =~ /\*/
      files += Dir[target]
    else
      files << target
    end
  end

  files.map { |f| File.expand_path(f) }.uniq
end
trap_interrupt() click to toggle source
# File lib/rubocop/cli.rb, line 226
def trap_interrupt
  Signal.trap('INT') do
    exit!(1) if wants_to_quit?
    self.wants_to_quit = true
    $stderr.puts
    $stderr.puts 'Exiting... Interrupt again to exit immediately.'
  end
end
validate_only_option() click to toggle source
# File lib/rubocop/cli.rb, line 73
def validate_only_option
  if @cops.none? { |c| c.cop_name == @options[:only] }
    fail ArgumentError, "Unrecognized cop name: #{@options[:only]}."
  end
end

Private Instance Methods

deprecate(subject, alternative = nil, version = nil) click to toggle source
# File lib/rubocop/cli.rb, line 395
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
excluded_file?(file) click to toggle source
# File lib/rubocop/cli.rb, line 403
def excluded_file?(file)
  ConfigStore.for(file).file_to_exclude?(file)
end
formatter_set() click to toggle source
# File lib/rubocop/cli.rb, line 381
def formatter_set
  @formatter_set ||= begin
    set = Formatter::FormatterSet.new(!@options[:silent])
    pairs = @options[:formatters] || [[DEFAULT_FORMATTER]]
    pairs.each do |formatter_key, output_path|
      set.add_formatter(formatter_key, output_path)
    end
    set
  rescue => error
    warn error.message
    exit(1)
  end
end
log_error(e, msg = '') click to toggle source
# File lib/rubocop/cli.rb, line 374
def log_error(e, msg = '')
  if @options[:debug]
    error_message = "#{e.class}, #{e.message}"
    warn "#{msg}\t#{error_message}"
  end
end