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.
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/VERSION +1 -1
- data/bin/rubocop +10 -2
- data/lib/rubocop.rb +16 -1
- data/lib/rubocop/cli.rb +48 -13
- data/lib/rubocop/cop/cop.rb +60 -3
- data/lib/rubocop/cop/empty_lines.rb +25 -0
- data/lib/rubocop/cop/encoding.rb +17 -0
- data/lib/rubocop/cop/grammar.rb +74 -0
- data/lib/rubocop/cop/indentation.rb +55 -0
- data/lib/rubocop/cop/line_length.rb +19 -0
- data/lib/rubocop/cop/offence.rb +12 -4
- data/lib/rubocop/cop/space_after_comma_etc.rb +24 -0
- data/lib/rubocop/cop/surrounding_space.rb +44 -0
- data/lib/rubocop/cop/tab.rb +17 -0
- data/lib/rubocop/cop/trailing_whitespace.rb +17 -0
- data/lib/rubocop/report/emacs_style.rb +15 -0
- data/lib/rubocop/report/plain_text.rb +18 -0
- data/lib/rubocop/report/report.rb +41 -0
- data/lib/rubocop/version.rb +2 -0
- data/rubocop.gemspec +103 -0
- data/spec/rubocop/cli_spec.rb +61 -0
- data/spec/rubocop/cops/cop_spec.rb +29 -0
- data/spec/rubocop/cops/empty_lines_spec.rb +82 -0
- data/spec/rubocop/cops/grammar_spec.rb +24 -0
- data/spec/rubocop/cops/indentation_spec.rb +60 -0
- data/spec/rubocop/cops/line_length_spec.rb +20 -0
- data/spec/rubocop/cops/offence_spec.rb +22 -0
- data/spec/rubocop/cops/space_after_comma_etc_spec.rb +37 -0
- data/spec/rubocop/cops/surrounding_space_spec.rb +128 -0
- data/spec/rubocop/cops/tab_spec.rb +19 -0
- data/spec/rubocop/cops/trailing_whitespace_spec.rb +25 -0
- data/spec/rubocop/reports/emacs_style_spec.rb +23 -0
- data/spec/rubocop/reports/report_spec.rb +27 -0
- data/spec/spec_helper.rb +25 -1
- metadata +88 -18
- data/lib/rubocop/cop/line_length_cop.rb +0 -11
- data/spec/rubocop_spec.rb +0 -7
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -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.
|
1
|
+
0.1.0
|
data/bin/rubocop
CHANGED
@@ -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
|
-
|
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
|
data/lib/rubocop.rb
CHANGED
@@ -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/
|
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'
|
data/lib/rubocop/cli.rb
CHANGED
@@ -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
|
-
|
8
|
-
|
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
|
-
|
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
|
-
|
19
|
-
|
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.
|
25
|
-
cop.
|
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
|
-
|
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
|
33
|
-
return Dir['**/*.rb'] if
|
65
|
+
def raw_target_files(args)
|
66
|
+
return Dir['**/*.rb'] if args.empty?
|
34
67
|
|
35
|
-
if glob =
|
36
|
-
|
68
|
+
if glob = args.detect { |arg| arg =~ /\*/ }
|
69
|
+
Dir[glob]
|
70
|
+
else
|
71
|
+
args
|
37
72
|
end
|
38
73
|
end
|
39
74
|
end
|
data/lib/rubocop/cop/cop.rb
CHANGED
@@ -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
|
11
|
-
|
12
|
-
|
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
|
data/lib/rubocop/cop/offence.rb
CHANGED
@@ -1,17 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
module Rubocop
|
2
4
|
module Cop
|
3
5
|
class Offence
|
4
|
-
attr_accessor :
|
6
|
+
attr_accessor :severity, :line_number, :line, :message
|
7
|
+
|
8
|
+
SEVERITIES = [:refactor, :convention, :warning, :error, :fatal]
|
5
9
|
|
6
|
-
def initialize(
|
7
|
-
@
|
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
|
-
"
|
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
|