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.



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

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.



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

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.



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

def wants_to_quit
  @wants_to_quit
end

Class Method Details

.parse(file) {|source_buffer| ... } ⇒ Object

Yields:

  • (source_buffer)


222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/rubocop/cli.rb', line 222

def self.parse(file)
  parser = Parser::CurrentRuby.new

  parser.diagnostics.all_errors_are_fatal = true
  parser.diagnostics.ignore_warnings      = true

  parser.diagnostics.consumer = lambda do |diagnostic|
    $stderr.puts(diagnostic.render)
  end

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

  ast, comments, tokens = parser.tokenize(source_buffer)

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

  [ast, comments, tokens, source_buffer.source.split($RS)]
end

Instance Method Details

#disabled_lines_in(source) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/rubocop/cli.rb', line 191

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



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/rubocop/cli.rb', line 169

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



213
214
215
216
217
218
219
220
# File 'lib/rubocop/cli.rb', line 213

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) ⇒ Object



120
121
122
123
124
125
126
127
128
# File 'lib/rubocop/cli.rb', line 120

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, config, report) ⇒ Object



79
80
81
82
83
84
85
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
# File 'lib/rubocop/cli.rb', line 79

def inspect_file(file, config, report)
  begin
    ast, comments, tokens, source = CLI.parse(file) do |source_buffer|
      source_buffer.read
    end
  rescue Parser::SyntaxError, Encoding::UndefinedConversionError,
    ArgumentError => e
    handle_error(e, "An error occurred while parsing #{file}.".color(:red))
    return
  end

  disabled_lines = disabled_lines_in(source)

  @cops.each do |cop_class|
    cop_name = cop_class.cop_name
    cop_class.config = config.for_cop(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, tokens, ast, comments)
        rescue => e
          handle_error(e,
                       "An error occurred while #{cop.name}".color(:red) +
                       " cop was inspecting #{file}.".color(:red))
        end
      end
      @total_offences += cop.offences.count
      report << cop if cop.has_report?
    end
  end
end

#parse_options(args) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/rubocop/cli.rb', line 130

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

#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



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/rubocop/cli.rb', line 276

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|
    if 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
  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



29
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
# File 'lib/rubocop/cli.rb', line 29

def run(args = ARGV)
  trap_interrupt

  parse_options(args)

  begin
    validate_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])

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

    syntax_cop = Rubocop::Cop::Syntax.new
    syntax_cop.debug = @options[:debug]
    syntax_cop.inspect_file(file)

    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, 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

#setup_cop(cop_class, disabled_lines) ⇒ Object



113
114
115
116
117
118
# File 'lib/rubocop/cli.rb', line 113

def setup_cop(cop_class, disabled_lines)
  cop = cop_class.new
  cop.debug = @options[:debug]
  cop.disabled_lines = disabled_lines[cop_class.cop_name]
  cop
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



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/rubocop/cli.rb', line 250

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



160
161
162
163
164
165
166
167
# File 'lib/rubocop/cli.rb', line 160

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_optionObject



73
74
75
76
77
# 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