rubocop 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubocop might be problematic. Click here for more details.

Files changed (39) hide show
  1. data/Gemfile +1 -0
  2. data/Gemfile.lock +2 -0
  3. data/VERSION +1 -1
  4. data/bin/rubocop +10 -2
  5. data/lib/rubocop.rb +16 -1
  6. data/lib/rubocop/cli.rb +48 -13
  7. data/lib/rubocop/cop/cop.rb +60 -3
  8. data/lib/rubocop/cop/empty_lines.rb +25 -0
  9. data/lib/rubocop/cop/encoding.rb +17 -0
  10. data/lib/rubocop/cop/grammar.rb +74 -0
  11. data/lib/rubocop/cop/indentation.rb +55 -0
  12. data/lib/rubocop/cop/line_length.rb +19 -0
  13. data/lib/rubocop/cop/offence.rb +12 -4
  14. data/lib/rubocop/cop/space_after_comma_etc.rb +24 -0
  15. data/lib/rubocop/cop/surrounding_space.rb +44 -0
  16. data/lib/rubocop/cop/tab.rb +17 -0
  17. data/lib/rubocop/cop/trailing_whitespace.rb +17 -0
  18. data/lib/rubocop/report/emacs_style.rb +15 -0
  19. data/lib/rubocop/report/plain_text.rb +18 -0
  20. data/lib/rubocop/report/report.rb +41 -0
  21. data/lib/rubocop/version.rb +2 -0
  22. data/rubocop.gemspec +103 -0
  23. data/spec/rubocop/cli_spec.rb +61 -0
  24. data/spec/rubocop/cops/cop_spec.rb +29 -0
  25. data/spec/rubocop/cops/empty_lines_spec.rb +82 -0
  26. data/spec/rubocop/cops/grammar_spec.rb +24 -0
  27. data/spec/rubocop/cops/indentation_spec.rb +60 -0
  28. data/spec/rubocop/cops/line_length_spec.rb +20 -0
  29. data/spec/rubocop/cops/offence_spec.rb +22 -0
  30. data/spec/rubocop/cops/space_after_comma_etc_spec.rb +37 -0
  31. data/spec/rubocop/cops/surrounding_space_spec.rb +128 -0
  32. data/spec/rubocop/cops/tab_spec.rb +19 -0
  33. data/spec/rubocop/cops/trailing_whitespace_spec.rb +25 -0
  34. data/spec/rubocop/reports/emacs_style_spec.rb +23 -0
  35. data/spec/rubocop/reports/report_spec.rb +27 -0
  36. data/spec/spec_helper.rb +25 -1
  37. metadata +88 -18
  38. data/lib/rubocop/cop/line_length_cop.rb +0 -11
  39. data/spec/rubocop_spec.rb +0 -7
data/Gemfile CHANGED
@@ -8,6 +8,7 @@ source "http://rubygems.org"
8
8
  group :development do
9
9
  gem "rspec", "~> 2.8.0"
10
10
  gem "yard", "~> 0.7"
11
+ gem "redcarpet"
11
12
  gem "cucumber", ">= 0"
12
13
  gem "bundler", "~> 1.1.0"
13
14
  gem "jeweler", "~> 1.8.3"
@@ -22,6 +22,7 @@ GEM
22
22
  rake (0.9.2.2)
23
23
  rdoc (3.12)
24
24
  json (~> 1.4)
25
+ redcarpet (2.1.1)
25
26
  rspec (2.8.0)
26
27
  rspec-core (~> 2.8.0)
27
28
  rspec-expectations (~> 2.8.0)
@@ -44,6 +45,7 @@ DEPENDENCIES
44
45
  bundler (~> 1.1.0)
45
46
  cucumber
46
47
  jeweler (~> 1.8.3)
48
+ redcarpet
47
49
  rspec (~> 2.8.0)
48
50
  simplecov
49
51
  yard (~> 0.7)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.1.0
@@ -1,8 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
3
+ $LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
4
4
 
5
5
  require 'rubocop'
6
+ require 'benchmark'
6
7
 
7
8
  cli = Rubocop::CLI.new
8
- exit cli.run
9
+ result = 0
10
+
11
+ time = Benchmark.realtime do
12
+ result = cli.run
13
+ end
14
+
15
+ puts "Finished in #{time} seconds"
16
+ exit result
@@ -1,5 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ require 'ripper'
4
+
1
5
  require 'rubocop/cop/offence'
2
6
  require 'rubocop/cop/cop'
3
- require 'rubocop/cop/line_length_cop'
7
+ require 'rubocop/cop/encoding'
8
+ require 'rubocop/cop/line_length'
9
+ require 'rubocop/cop/tab'
10
+ require 'rubocop/cop/trailing_whitespace'
11
+ require 'rubocop/cop/indentation'
12
+ require 'rubocop/cop/empty_lines'
13
+ require 'rubocop/cop/surrounding_space'
14
+ require 'rubocop/cop/space_after_comma_etc'
15
+
16
+ require 'rubocop/report/report'
17
+ require 'rubocop/report/plain_text'
18
+ require 'rubocop/report/emacs_style'
4
19
 
5
20
  require 'rubocop/cli'
@@ -1,11 +1,17 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'optparse'
2
4
 
3
5
  module Rubocop
4
6
  # The CLI is a class responsible of handling all the command line interface
5
7
  # logic.
6
8
  class CLI
7
- def run
8
- options = {}
9
+ # Entry point for the application logic. Here we
10
+ # do the command line arguments processing and inspect
11
+ # the target files
12
+ # @return [Fixnum] UNIX exit code
13
+ def run(args = ARGV)
14
+ options = { :mode => :default }
9
15
 
10
16
  OptionParser.new do |opts|
11
17
  opts.banner = "Usage: rubocop [options] [file1, file2, ...]"
@@ -13,27 +19,56 @@ module Rubocop
13
19
  opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
14
20
  options[:verbose] = v
15
21
  end
16
- end.parse!
22
+ opts.on("-e", "--emacs", "Emacs style output") do
23
+ options[:mode] = :emacs_style
24
+ end
25
+ end.parse!(args)
26
+
27
+ cops = Cop::Cop.all
28
+ total_offences = 0
29
+
30
+ target_files(args).reject { |f| File.directory?(f) }.each do |file|
31
+ report = Report.create(file, options[:mode])
32
+ source = File.readlines(file).map { |line|
33
+ enc = line.encoding.name
34
+ # Get rid of invalid byte sequences
35
+ line.encode!('UTF-16', enc, :invalid => :replace, :replace => '')
36
+ line.encode!(enc, 'UTF-16')
17
37
 
18
- cops = []
19
- cops << Cop::LineLengthCop
38
+ line.chomp
39
+ }
20
40
 
21
- target_files.each do |file|
22
41
  cops.each do |cop_klass|
23
42
  cop = cop_klass.new
24
- cop.inspect(file)
25
- cop.report
43
+ cop.inspect_source(file, source)
44
+ total_offences += cop.offences.count
45
+ report << cop if cop.has_report?
26
46
  end
47
+
48
+ report.display unless report.empty?
27
49
  end
28
50
 
29
- return 0
51
+ print "\n#{target_files(args).count} files inspected, "
52
+ puts "#{total_offences} offences detected"
53
+
54
+ return total_offences == 0 ? 0 : 1
55
+ end
56
+
57
+ # Generate a list of target files by expanding globing patterns
58
+ # (if any). If args is empty recursively finds all Ruby source
59
+ # files in the current directory
60
+ # @return [Array] array of filenames
61
+ def target_files(args)
62
+ raw_target_files(args).reject { |name| name =~ /_flymake/ }
30
63
  end
31
64
 
32
- def target_files
33
- return Dir['**/*.rb'] if ARGV.empty?
65
+ def raw_target_files(args)
66
+ return Dir['**/*.rb'] if args.empty?
34
67
 
35
- if glob = ARGV.detect { |arg| arg =~ /\*/ }
36
- return Dir[glob]
68
+ if glob = args.detect { |arg| arg =~ /\*/ }
69
+ Dir[glob]
70
+ else
71
+ args
37
72
  end
38
73
  end
39
74
  end
@@ -1,21 +1,78 @@
1
+ # encoding: utf-8
2
+
1
3
  module Rubocop
2
4
  module Cop
3
5
  class Cop
4
6
  attr_accessor :offences
5
7
 
8
+ @all = []
9
+ @enabled = []
10
+ @config = {}
11
+
12
+ class << self
13
+ attr_accessor :all
14
+ attr_accessor :enabled
15
+ attr_accessor :config
16
+ end
17
+
18
+ def self.inherited(subclass)
19
+ puts "Registering cop #{subclass}"
20
+ all << subclass
21
+ end
22
+
23
+ def self.enabled
24
+ all.select(&:enabled?)
25
+ end
26
+
27
+ def self.enabled?
28
+ true
29
+ end
30
+
6
31
  def initialize
7
32
  @offences = []
8
33
  end
9
34
 
10
- def report
11
- @offences.each do |offence|
12
- puts offence
35
+ def has_report?
36
+ !@offences.empty?
37
+ end
38
+
39
+ def inspect_source(file, source)
40
+ case method(:inspect).arity
41
+ when 2
42
+ inspect(file, source)
43
+ else
44
+ tokens = Ripper.lex(source.join("\n"))
45
+ sexp = Ripper.sexp(source.join("\n"))
46
+ inspect(file, source, tokens, sexp)
13
47
  end
14
48
  end
15
49
 
16
50
  def add_offence(file, line_number, line, message)
17
51
  @offences << Offence.new(file, line_number, line, message)
18
52
  end
53
+
54
+ private
55
+
56
+ def each_parent_of(sym, sexp)
57
+ parents = []
58
+ sexp.each { |elem|
59
+ if Array === elem
60
+ if elem[0] == sym
61
+ parents << sexp
62
+ elem = elem[1..-1]
63
+ end
64
+ each_parent_of(sym, elem) { |parent| parents << parent }
65
+ end
66
+ }
67
+ parents.uniq.each { |parent| yield parent }
68
+ end
69
+
70
+ def each(sym, sexp)
71
+ yield sexp if sexp[0] == sym
72
+ sexp.each { |elem|
73
+ each(sym, elem) { |s| yield s } if Array === elem
74
+ }
75
+ end
19
76
  end
20
77
  end
21
78
  end
@@ -0,0 +1,25 @@
1
+ module Rubocop
2
+ module Cop
3
+ class EmptyLines < Cop
4
+ ERROR_MESSAGE = 'Use empty lines between defs.'
5
+
6
+ def inspect(file, source, tokens, sexp)
7
+ each_parent_of(:def, sexp) do |parent|
8
+ defs = parent.select { |child| child[0] == :def }
9
+ identifier_of_first_def = defs[0][1]
10
+ current_row_ix = identifier_of_first_def[-1][0] - 1
11
+ # The first def doesn't need to have an empty line above it,
12
+ # so we iterate starting at index 1.
13
+ defs[1..-1].each { |child|
14
+ next_row_ix = child[1][-1][0] - 1
15
+ if source[current_row_ix..next_row_ix].grep(/^[ \t]*$/).empty?
16
+ add_offence(:convention, next_row_ix, source[next_row_ix],
17
+ ERROR_MESSAGE)
18
+ end
19
+ current_row_ix = next_row_ix
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class Encoding < Cop
6
+ ERROR_MESSAGE = 'Missing encoding comment.'
7
+ MAX_LINE_LENGTH = 80
8
+
9
+ def inspect(file, source, tokens, sexp)
10
+ unless source[0] =~ /#.*coding: (UTF|utf)-8/
11
+ message = sprintf(ERROR_MESSAGE)
12
+ add_offence(:convention, 0, 0, message)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,74 @@
1
+ module Rubocop
2
+ module Cop
3
+ class Grammar
4
+ def initialize(tokens)
5
+ @tokens_without_pos = tokens.map { |tok| tok[1..-1] }
6
+ @ix = 0
7
+ @table = {}
8
+ token_positions = tokens.map { |tok| tok[0] }
9
+ @index_by_pos = Hash[*token_positions.each_with_index.to_a.flatten(1)]
10
+ @special = { assign: '=' }
11
+ end
12
+
13
+ # Returns a hash mapping indexes in the token array to grammar
14
+ # paths, e.g.:
15
+ # { 0 => [:program, :assign, :var_field, :@ident],
16
+ # 1 => [:program, :assign],
17
+ # 2 => [:program, :assign, :@int],
18
+ # 4 => [:program, :assign, :var_field, :@ident],
19
+ # 5 => [:program, :assign],
20
+ # 7 => [:program, :assign, :@int],
21
+ # 9 => [:program, :assign, :var_field, :@ident],
22
+ # 11 => [:program, :assign],
23
+ # 12 => [:program, :assign, :@int] }
24
+ def correlate(sexp, path = [])
25
+ case sexp
26
+ when Array
27
+ case sexp[0]
28
+ when /^@/
29
+ # Leaves in the grammar have a corresponding token with a
30
+ # position, which we search for and advance @ix.
31
+ @ix = @index_by_pos[sexp[-1]]
32
+ @table[@ix] = path + [sexp[0]]
33
+ @ix += 1
34
+ when *@special.keys
35
+ # Here we don't advance @ix because there may be other
36
+ # tokens inbetween the current one and the one we get from
37
+ # @special.
38
+ find(path, sexp, [:on_op, @special[sexp[0]]])
39
+ when :block_var # "{ |...|" or "do |...|"
40
+ @ix = find(path, sexp, [:on_op, '|']) + 1
41
+ find(path, sexp, [:on_op, '|'])
42
+ end
43
+ path += [sexp[0]] if Symbol === sexp[0]
44
+ # Compensate for reverse order of if modifier
45
+ children = (sexp[0] == :if_mod) ? sexp.reverse : sexp
46
+
47
+ children.each { |elem|
48
+ case elem
49
+ when Array
50
+ correlate(elem, path) # Dive deeper
51
+ when Symbol
52
+ unless elem.to_s =~ /^@?[a-z_]+$/
53
+ # There's a trailing @ in some symbols in sexp,
54
+ # e.g. :-@, that don't appear in tokens. That's why we
55
+ # chomp it off.
56
+ find(path, [elem], [:on_op, elem.to_s.chomp('@')])
57
+ end
58
+ end
59
+ }
60
+ end
61
+ @table
62
+ end
63
+
64
+ private
65
+
66
+ def find(path, sexp, token_to_find)
67
+ offset = @tokens_without_pos[@ix..-1].index(token_to_find) or return
68
+ ix = @ix + offset
69
+ @table[ix] = path + [sexp[0]]
70
+ ix
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,55 @@
1
+ module Rubocop
2
+ module Cop
3
+ class Indentation < Cop
4
+ ERROR_MESSAGE = 'Indent when as deep as case.'
5
+
6
+ def inspect(file, source, tokens, sexp)
7
+ case_tokens = find_keywords(tokens, 'case')
8
+ when_tokens = find_keywords(tokens, 'when')
9
+ each_when(sexp) { |case_ix|
10
+ when_pos = when_tokens.shift[0]
11
+ if when_pos[1] != case_tokens[case_ix][0][1]
12
+ index = when_pos[0] - 1
13
+ add_offence(:convention, index, source[index], ERROR_MESSAGE)
14
+ end
15
+ }
16
+ end
17
+
18
+ def find_keywords(tokens, keyword)
19
+ indexes = tokens.each_index.find_all { |ix|
20
+ keyword?(tokens, ix, keyword)
21
+ }
22
+ tokens.values_at(*indexes)
23
+ end
24
+
25
+ def keyword?(tokens, ix, keyword)
26
+ tokens[ix][1..-1] == [:on_kw, keyword] &&
27
+ tokens[ix - 1][1] != :on_symbeg
28
+ end
29
+
30
+ # Does a depth first search for :when, yielding the index of the
31
+ # corresponding :case for each one.
32
+ def each_when(sexp, case_ix = -1, &block)
33
+ case sexp[0]
34
+ when :case
35
+ case_ix += 1
36
+ case_ix = next_when(sexp, case_ix, &block)
37
+ when :when
38
+ yield case_ix
39
+ all_except_when = sexp.grep(Array).find_all { |s| s[0] != :when }
40
+ case_ix_deep = each_when(all_except_when, case_ix, &block)
41
+ case_ix_next = next_when(sexp, case_ix, &block)
42
+ case_ix = (case_ix_next == case_ix) ? case_ix_deep : case_ix_next
43
+ else
44
+ sexp.grep(Array).each { |s| case_ix = each_when(s, case_ix, &block) }
45
+ end
46
+ case_ix
47
+ end
48
+
49
+ def next_when(sexp, case_ix, &block)
50
+ nxt = sexp.grep(Array).find { |s| s[0] == :when } or return case_ix
51
+ each_when(nxt, case_ix, &block)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class LineLength < Cop
6
+ ERROR_MESSAGE = 'Line is too long. [%d/%d]'
7
+ MAX_LINE_LENGTH = 79
8
+
9
+ def inspect(file, source)
10
+ source.each_with_index do |line, index|
11
+ if line.length > MAX_LINE_LENGTH
12
+ message = sprintf(ERROR_MESSAGE, line.length, MAX_LINE_LENGTH)
13
+ add_offence(:convention, index, line, message)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,17 +1,25 @@
1
+ # encoding: utf-8
2
+
1
3
  module Rubocop
2
4
  module Cop
3
5
  class Offence
4
- attr_accessor :filename, :line_number, :line, :message
6
+ attr_accessor :severity, :line_number, :line, :message
7
+
8
+ SEVERITIES = [:refactor, :convention, :warning, :error, :fatal]
5
9
 
6
- def initialize(filename, line_number, line, message)
7
- @filename = filename
10
+ def initialize(severity, line_number, line, message)
11
+ @severity = severity
8
12
  @line_number = line_number
9
13
  @line = line
10
14
  @message = message
11
15
  end
12
16
 
13
17
  def to_s
14
- "#@filename:#@line_number:#{@line.chomp} - #@message"
18
+ "#{encode_severity}:#{sprintf("%3d", line_number)}: #{message}"
19
+ end
20
+
21
+ def encode_severity
22
+ @severity.to_s[0].upcase
15
23
  end
16
24
  end
17
25
  end