Class: Rubocop::CLI

Inherits:
Object
  • Object
show all
Defined in:
lib/rubocop/cli.rb

Overview

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCLI

Returns a new instance of CLI.



17
18
19
20
21
22
23
24
# File 'lib/rubocop/cli.rb', line 17

def initialize
  @cops = Cop::Cop.all
  @processed_file_count = 0
  @total_offences = 0
  @errors = []
  @options = { mode: :default }
  ConfigStore.prepare
end

Instance Attribute Details

#optionsObject

Returns the value of attribute options.



13
14
15
# File 'lib/rubocop/cli.rb', line 13

def options
  @options
end

#wants_to_quitObject Also known as: wants_to_quit?

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



12
13
14
# File 'lib/rubocop/cli.rb', line 12

def wants_to_quit
  @wants_to_quit
end

Class Method Details

.rip_source(source) ⇒ Object



219
220
221
222
223
224
225
# File 'lib/rubocop/cli.rb', line 219

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

Instance Method Details

#disabled_lines_in(source) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/rubocop/cli.rb', line 179

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_summary(num_files, total_offences, errors) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/rubocop/cli.rb', line 157

def display_summary(num_files, total_offences, errors)
  plural = num_files == 0 || num_files > 1 ? 's' : ''
  print "\n#{num_files} file#{plural} inspected, "
  offences_string = if total_offences.zero?
                      'no offences'
                    elsif total_offences == 1
                      '1 offence'
                    else
                      "#{total_offences} offences"
                    end
  puts "#{offences_string} detected"
    .color(total_offences.zero? ? :green : :red)

  if errors.count > 0
    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.'
  end
end

#each_mentioned_cop(regexp, line) ⇒ Object



201
202
203
204
205
206
207
208
# File 'lib/rubocop/cli.rb', line 201

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

#get_rid_of_invalid_byte_sequences(source) ⇒ Object



210
211
212
213
214
215
216
217
# File 'lib/rubocop/cli.rb', line 210

def get_rid_of_invalid_byte_sequences(source)
  source_encoding = source.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'
  source.encode!(temporary_encoding, source_encoding,
                 invalid: :replace, replace: '')
  source.encode!(source_encoding, temporary_encoding)
end

#handle_only_optionObject



75
76
77
78
79
80
# File 'lib/rubocop/cli.rb', line 75

def handle_only_option
  @cops = @cops.select { |c| c.cop_name == @options[:only] }
  if @cops.empty?
    fail ArgumentError, "Unrecognized cop name: #{@options[:only]}."
  end
end

#inspect_file(file, source, config, report) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/rubocop/cli.rb', line 86

def inspect_file(file, source, config, report)
  tokens, sexp, correlations = CLI.rip_source(source)
  disabled_lines = disabled_lines_in(source)

  @cops.each do |cop_klass|
    cop_name = cop_klass.cop_name
    cop_config = config.for_cop(cop_name)
    if config.cop_enabled?(cop_name)
      cop_klass.config = cop_config
      cop = cop_klass.new
      cop.debug = @options[:debug]
      cop.correlations = correlations
      cop.disabled_lines = disabled_lines[cop_name]
      begin
        cop.inspect(file, source, tokens, sexp)
      rescue => e
        message = "An error occurred while #{cop.name} cop".color(:red) +
          " was inspecting #{file}.".color(:red)
        @errors << message
        warn message
        if @options[:debug]
          puts e.message, e.backtrace
        else
          warn 'To see the complete backtrace run rubocop -d.'
        end
      end
      @total_offences += cop.offences.count
      report << cop if cop.has_report?
    end
  end
end

#parse_options(args) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/rubocop/cli.rb', line 118

def parse_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('-e', '--emacs', 'Emacs style output') do
      @options[:mode] = :emacs_style
    end
    opts.on('-c FILE', '--config FILE', '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
    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::STRING
      exit(0)
    end
  end.parse!(args)
end

#read_source(file) ⇒ Object



82
83
84
# File 'lib/rubocop/cli.rb', line 82

def read_source(file)
  get_rid_of_invalid_byte_sequences(File.read(file)).split($RS)
end

#ruby_files(root = Dir.pwd) ⇒ Array

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.

Parameters:

  • root (defaults to: Dir.pwd)

    Root directory under which to search for ruby source files

Returns:

  • (Array)

    Array of filenames



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/rubocop/cli.rb', line 257

def ruby_files(root = Dir.pwd)
  files = Dir["#{root}/**/*"].reject { |file| FileTest.directory? file }

  rb = []

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

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

  rb.reject do |file|
    config = ConfigStore.for(file)
    config.file_to_exclude?(file)
  end.uniq
end

#run(args = ARGV) ⇒ Fixnum

Entry point for the application logic. Here we do the command line arguments processing and inspect the target files

Returns:

  • (Fixnum)

    UNIX exit code



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/rubocop/cli.rb', line 30

def run(args = ARGV)
  trap_interrupt

  parse_options(args)

  begin
    handle_only_option if @options[:only]
  rescue ArgumentError => e
    puts e.message
    return 1
  end

  target_files(args).each do |file|
    break if wants_to_quit?

    config = ConfigStore.for(file)
    report = Report.create(file, @options[:mode])
    source = read_source(file)

    puts "Scanning #{file}" if @options[:debug]

    syntax_cop = Rubocop::Cop::Syntax.new
    syntax_cop.debug = @options[:debug]
    syntax_cop.inspect(file, source, nil, nil)

    if syntax_cop.offences.map(&:severity).include?(:error)
      # In case of a syntax error we just report that error and do
      # no more checking in the file.
      report << syntax_cop
      @total_offences += syntax_cop.offences.count
    else
      inspect_file(file, source, config, report)
    end

    @processed_file_count += 1
    report.display unless report.empty?
  end

  unless @options[:silent]
    display_summary(@processed_file_count, @total_offences, @errors)
  end

  (@total_offences == 0) && !wants_to_quit ? 0 : 1
end

#target_files(args) ⇒ Array

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

Returns:

  • (Array)

    array of filenames



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/rubocop/cli.rb', line 231

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.uniq
end

#trap_interruptObject



148
149
150
151
152
153
154
155
# File 'lib/rubocop/cli.rb', line 148

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