rubocop 0.1.0 → 0.2.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/.rbenv-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +8 -8
- data/README.md +17 -1
- data/VERSION +1 -1
- data/bin/rubocop +1 -1
- data/features/step_definitions/rubocop_steps.rb +1 -0
- data/features/support/env.rb +2 -0
- data/lib/rubocop.rb +6 -0
- data/lib/rubocop/cli.rb +37 -17
- data/lib/rubocop/cop/align_parameters.rb +112 -0
- data/lib/rubocop/cop/cop.rb +43 -21
- data/lib/rubocop/cop/def_parentheses.rb +38 -0
- data/lib/rubocop/cop/empty_lines.rb +7 -6
- data/lib/rubocop/cop/encoding.rb +1 -1
- data/lib/rubocop/cop/end_of_line.rb +17 -0
- data/lib/rubocop/cop/grammar.rb +69 -9
- data/lib/rubocop/cop/hash_syntax.rb +26 -0
- data/lib/rubocop/cop/if_then_else.rb +49 -0
- data/lib/rubocop/cop/indentation.rb +16 -27
- data/lib/rubocop/cop/line_length.rb +2 -2
- data/lib/rubocop/cop/numeric_literals.rb +19 -0
- data/lib/rubocop/cop/offence.rb +2 -3
- data/lib/rubocop/cop/space_after_comma_etc.rb +10 -9
- data/lib/rubocop/cop/surrounding_space.rb +66 -17
- data/lib/rubocop/cop/tab.rb +2 -2
- data/lib/rubocop/cop/trailing_whitespace.rb +2 -2
- data/lib/rubocop/report/emacs_style.rb +4 -3
- data/rubocop.gemspec +16 -2
- data/spec/rubocop/cli_spec.rb +20 -5
- data/spec/rubocop/cops/align_parameters_spec.rb +201 -0
- data/spec/rubocop/cops/cop_spec.rb +4 -2
- data/spec/rubocop/cops/def_parentheses_spec.rb +48 -0
- data/spec/rubocop/cops/empty_lines_spec.rb +9 -8
- data/spec/rubocop/cops/end_of_line_spec.rb +17 -0
- data/spec/rubocop/cops/grammar_spec.rb +51 -11
- data/spec/rubocop/cops/hash_syntax_spec.rb +44 -0
- data/spec/rubocop/cops/if_then_else_spec.rb +74 -0
- data/spec/rubocop/cops/indentation_spec.rb +29 -4
- data/spec/rubocop/cops/line_length_spec.rb +4 -2
- data/spec/rubocop/cops/numeric_literals_spec.rb +49 -0
- data/spec/rubocop/cops/offence_spec.rb +4 -3
- data/spec/rubocop/cops/space_after_comma_etc_spec.rb +7 -5
- data/spec/rubocop/cops/surrounding_space_spec.rb +89 -26
- data/spec/rubocop/cops/tab_spec.rb +4 -2
- data/spec/rubocop/cops/trailing_whitespace_spec.rb +5 -3
- data/spec/rubocop/reports/emacs_style_spec.rb +4 -2
- data/spec/rubocop/reports/report_spec.rb +3 -1
- data/spec/spec_helper.rb +9 -1
- metadata +17 -3
data/.rbenv-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p327
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
source
|
1
|
+
source 'http://rubygems.org'
|
2
2
|
# Add dependencies required to use your gem here.
|
3
3
|
# Example:
|
4
4
|
# gem "activesupport", ">= 2.3.5"
|
@@ -6,11 +6,11 @@ source "http://rubygems.org"
|
|
6
6
|
# Add dependencies to develop your gem here.
|
7
7
|
# Include everything needed to run rake, tests, features, etc.
|
8
8
|
group :development do
|
9
|
-
gem
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
14
|
-
gem
|
15
|
-
gem
|
9
|
+
gem 'rspec', '~> 2.8.0'
|
10
|
+
gem 'yard', '~> 0.7'
|
11
|
+
gem 'redcarpet'
|
12
|
+
gem 'cucumber', '>= 0'
|
13
|
+
gem 'bundler', '~> 1.1.0'
|
14
|
+
gem 'jeweler', '~> 1.8.3'
|
15
|
+
gem 'simplecov'
|
16
16
|
end
|
data/README.md
CHANGED
@@ -1,6 +1,22 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/bbatsov/rubocop.png?branch=master)](https://travis-ci.org/bbatsov/rubocop)
|
2
|
+
|
1
3
|
# rubocop
|
2
4
|
|
3
|
-
Ruby code style checker.
|
5
|
+
Ruby code style checker based on the [Ruby Style Guide](https://github.com/bbatsov/ruby-style-guide).
|
6
|
+
|
7
|
+
## Basic Usage
|
8
|
+
|
9
|
+
Running `rubocop` with no arguments will check all Ruby source files in the current folder:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
$ rubocop
|
13
|
+
```
|
14
|
+
|
15
|
+
Alternatively you can `rubocop` a list of files and folders to check:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
$ rubocop app spec lib/something.rb
|
19
|
+
```
|
4
20
|
|
5
21
|
## Contributing to Rubocop
|
6
22
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/bin/rubocop
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
# encoding: utf-8
|
data/features/support/env.rb
CHANGED
data/lib/rubocop.rb
CHANGED
@@ -12,6 +12,12 @@ require 'rubocop/cop/indentation'
|
|
12
12
|
require 'rubocop/cop/empty_lines'
|
13
13
|
require 'rubocop/cop/surrounding_space'
|
14
14
|
require 'rubocop/cop/space_after_comma_etc'
|
15
|
+
require 'rubocop/cop/hash_syntax'
|
16
|
+
require 'rubocop/cop/end_of_line'
|
17
|
+
require 'rubocop/cop/numeric_literals'
|
18
|
+
require 'rubocop/cop/align_parameters'
|
19
|
+
require 'rubocop/cop/def_parentheses'
|
20
|
+
require 'rubocop/cop/if_then_else'
|
15
21
|
|
16
22
|
require 'rubocop/report/report'
|
17
23
|
require 'rubocop/report/plain_text'
|
data/lib/rubocop/cli.rb
CHANGED
@@ -11,36 +11,39 @@ module Rubocop
|
|
11
11
|
# the target files
|
12
12
|
# @return [Fixnum] UNIX exit code
|
13
13
|
def run(args = ARGV)
|
14
|
-
options = { :
|
14
|
+
$options = { mode: :default }
|
15
15
|
|
16
16
|
OptionParser.new do |opts|
|
17
17
|
opts.banner = "Usage: rubocop [options] [file1, file2, ...]"
|
18
18
|
|
19
19
|
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
20
|
-
options[:verbose] = v
|
20
|
+
$options[:verbose] = v
|
21
21
|
end
|
22
22
|
opts.on("-e", "--emacs", "Emacs style output") do
|
23
|
-
options[:mode] = :emacs_style
|
23
|
+
$options[:mode] = :emacs_style
|
24
24
|
end
|
25
25
|
end.parse!(args)
|
26
26
|
|
27
27
|
cops = Cop::Cop.all
|
28
|
+
show_cops_on_duty(cops) if $options[:verbose]
|
28
29
|
total_offences = 0
|
29
30
|
|
30
|
-
target_files(args).
|
31
|
-
report = Report.create(file, options[:mode])
|
32
|
-
source = File.readlines(file).map
|
31
|
+
target_files(args).each do |file|
|
32
|
+
report = Report.create(file, $options[:mode])
|
33
|
+
source = File.readlines(file).map do |line|
|
33
34
|
enc = line.encoding.name
|
34
35
|
# Get rid of invalid byte sequences
|
35
|
-
line.encode!('UTF-16', enc, :
|
36
|
+
line.encode!('UTF-16', enc, invalid: :replace, replace: '')
|
36
37
|
line.encode!(enc, 'UTF-16')
|
37
38
|
|
38
39
|
line.chomp
|
39
|
-
|
40
|
+
end
|
41
|
+
|
42
|
+
tokens, sexp = CLI.rip_source(source)
|
40
43
|
|
41
44
|
cops.each do |cop_klass|
|
42
45
|
cop = cop_klass.new
|
43
|
-
cop.
|
46
|
+
cop.inspect(file, source, tokens, sexp)
|
44
47
|
total_offences += cop.offences.count
|
45
48
|
report << cop if cop.has_report?
|
46
49
|
end
|
@@ -54,22 +57,39 @@ module Rubocop
|
|
54
57
|
return total_offences == 0 ? 0 : 1
|
55
58
|
end
|
56
59
|
|
60
|
+
def self.rip_source(source)
|
61
|
+
tokens = Ripper.lex(source.join("\n")).map { |t| Cop::Token.new(*t) }
|
62
|
+
sexp = Ripper.sexp(source.join("\n"))
|
63
|
+
Cop::Position.make_position_objects(sexp)
|
64
|
+
[tokens, sexp]
|
65
|
+
end
|
66
|
+
|
67
|
+
def show_cops_on_duty(cops)
|
68
|
+
puts "Reporting for duty:"
|
69
|
+
cops.each { |c| puts c }
|
70
|
+
puts "*******************"
|
71
|
+
end
|
72
|
+
|
57
73
|
# Generate a list of target files by expanding globing patterns
|
58
74
|
# (if any). If args is empty recursively finds all Ruby source
|
59
75
|
# files in the current directory
|
60
76
|
# @return [Array] array of filenames
|
61
77
|
def target_files(args)
|
62
|
-
raw_target_files(args).reject { |name| name =~ /_flymake/ }
|
63
|
-
end
|
64
|
-
|
65
|
-
def raw_target_files(args)
|
66
78
|
return Dir['**/*.rb'] if args.empty?
|
67
79
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
80
|
+
files = []
|
81
|
+
|
82
|
+
args.each do |target|
|
83
|
+
if File.directory?(target)
|
84
|
+
files << Dir["#{target}/**/*.rb"]
|
85
|
+
elsif target =~ /\*/
|
86
|
+
files << Dir[target]
|
87
|
+
else
|
88
|
+
files << target
|
89
|
+
end
|
72
90
|
end
|
91
|
+
|
92
|
+
files.flatten
|
73
93
|
end
|
74
94
|
end
|
75
95
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Rubocop
|
4
|
+
module Cop
|
5
|
+
class AlignParameters < Cop
|
6
|
+
ERROR_MESSAGE = 'Align the parameters of a method call if they span ' +
|
7
|
+
'more than one line.'
|
8
|
+
|
9
|
+
def inspect(file, source, tokens, sexp)
|
10
|
+
@file = file
|
11
|
+
@tokens = tokens
|
12
|
+
@token_indexes = {}
|
13
|
+
@tokens.each_with_index { |t, ix| @token_indexes[t.pos] = ix }
|
14
|
+
|
15
|
+
each(:method_add_arg, sexp) do |method_add_arg|
|
16
|
+
args = get_args(method_add_arg) or next
|
17
|
+
first_arg, rest_of_args = divide_args(args)
|
18
|
+
@first_lparen_ix = get_lparen_ix(method_add_arg)
|
19
|
+
pos_of_1st_arg = position_of(first_arg) or next # Give up.
|
20
|
+
rest_of_args.each do |arg|
|
21
|
+
pos = position_of(arg) or next # Give up if no position found.
|
22
|
+
if pos.lineno != pos_of_1st_arg.lineno
|
23
|
+
if pos.column != pos_of_1st_arg.column
|
24
|
+
add_offence(:convention, pos.lineno, ERROR_MESSAGE)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def get_args(method_add_arg)
|
34
|
+
fcall = method_add_arg[1]
|
35
|
+
return nil if fcall[0] != :fcall
|
36
|
+
return nil if fcall[1][0..1] == [:@ident, "lambda"]
|
37
|
+
arg_paren = method_add_arg[2..-1][0]
|
38
|
+
return nil if arg_paren[0] != :arg_paren || arg_paren[1].nil?
|
39
|
+
|
40
|
+
# A command (call wihtout parentheses) as first parameter
|
41
|
+
# means there's only one parameter.
|
42
|
+
return nil if [:command, :command_call].include?(arg_paren[1][0][0])
|
43
|
+
|
44
|
+
args_add_block = arg_paren[1]
|
45
|
+
unless args_add_block[0] == :args_add_block
|
46
|
+
fail "\n#{@file}: #{method_add_arg}"
|
47
|
+
end
|
48
|
+
args_add_block[1].empty? ? [args_add_block[2]] : args_add_block[1]
|
49
|
+
end
|
50
|
+
|
51
|
+
def divide_args(args)
|
52
|
+
if args[0] == :args_add_star
|
53
|
+
first_arg = args[1]
|
54
|
+
rest_of_args = args[2..-1]
|
55
|
+
else
|
56
|
+
first_arg = args[0]
|
57
|
+
rest_of_args = args[1..-1]
|
58
|
+
end
|
59
|
+
[first_arg, rest_of_args]
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_lparen_ix(method_add_arg)
|
63
|
+
method_name_pos = method_add_arg[1][1][-1]
|
64
|
+
method_name_ix = @token_indexes[method_name_pos]
|
65
|
+
method_name_ix +
|
66
|
+
@tokens[method_name_ix..-1].map(&:type).index(:on_lparen)
|
67
|
+
end
|
68
|
+
|
69
|
+
def position_of(sexp)
|
70
|
+
# Indentation inside a string literal is irrelevant.
|
71
|
+
return nil if sexp[0] == :string_literal
|
72
|
+
|
73
|
+
pos = find_pos_in_sexp(sexp) or return nil # Nil means not found.
|
74
|
+
ix = find_first_non_whitespace_token(pos) or return nil
|
75
|
+
@tokens[ix].pos
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_pos_in_sexp(sexp)
|
79
|
+
return sexp[2] if Position === sexp[2]
|
80
|
+
sexp.grep(Array).each do |s|
|
81
|
+
pos = find_pos_in_sexp(s) and return pos
|
82
|
+
end
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def find_first_non_whitespace_token(pos)
|
87
|
+
ix = @token_indexes[pos]
|
88
|
+
newline_found = false
|
89
|
+
start_ix = ix.downto(0) do |i|
|
90
|
+
case @tokens[i].text
|
91
|
+
when '('
|
92
|
+
break i + 1 if i == @first_lparen_ix
|
93
|
+
when "\n"
|
94
|
+
newline_found = true
|
95
|
+
when /\t/
|
96
|
+
# Bail out if tabs are used. Too difficult to calculate column.
|
97
|
+
return nil
|
98
|
+
when ','
|
99
|
+
if newline_found
|
100
|
+
break i + 1
|
101
|
+
else
|
102
|
+
# Bail out if there's a preceding comma on the same line.
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
offset = @tokens[start_ix..-1].index { |t| not whitespace?(t) }
|
108
|
+
start_ix + offset
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/rubocop/cop/cop.rb
CHANGED
@@ -2,6 +2,34 @@
|
|
2
2
|
|
3
3
|
module Rubocop
|
4
4
|
module Cop
|
5
|
+
class Position < Struct.new :lineno, :column
|
6
|
+
# Does a recursive search and replaces each [lineno, column] array
|
7
|
+
# in the sexp with a Position object.
|
8
|
+
def self.make_position_objects(sexp)
|
9
|
+
if sexp[0] =~ /^@/
|
10
|
+
sexp[2] = Position.new(*sexp[2])
|
11
|
+
else
|
12
|
+
sexp.grep(Array).each { |s| make_position_objects(s) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# The point of this class is to provide named attribute access.
|
17
|
+
# So we don't want backwards compatibility with array indexing.
|
18
|
+
undef_method :[]
|
19
|
+
end
|
20
|
+
|
21
|
+
class Token
|
22
|
+
attr_reader :pos, :type, :text
|
23
|
+
|
24
|
+
def initialize(pos, type, text)
|
25
|
+
@pos, @type, @text = Position.new(*pos), type, text
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"[[#{@pos.lineno}, #{@pos.column}], #@type, #{@text.inspect}]"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
5
33
|
class Cop
|
6
34
|
attr_accessor :offences
|
7
35
|
|
@@ -16,7 +44,6 @@ module Rubocop
|
|
16
44
|
end
|
17
45
|
|
18
46
|
def self.inherited(subclass)
|
19
|
-
puts "Registering cop #{subclass}"
|
20
47
|
all << subclass
|
21
48
|
end
|
22
49
|
|
@@ -36,42 +63,37 @@ module Rubocop
|
|
36
63
|
!@offences.empty?
|
37
64
|
end
|
38
65
|
|
39
|
-
def
|
40
|
-
|
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)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def add_offence(file, line_number, line, message)
|
51
|
-
@offences << Offence.new(file, line_number, line, message)
|
66
|
+
def add_offence(file, line_number, message)
|
67
|
+
@offences << Offence.new(file, line_number, message)
|
52
68
|
end
|
53
69
|
|
54
70
|
private
|
55
71
|
|
56
72
|
def each_parent_of(sym, sexp)
|
57
73
|
parents = []
|
58
|
-
sexp.each
|
74
|
+
sexp.each do |elem|
|
59
75
|
if Array === elem
|
60
76
|
if elem[0] == sym
|
61
|
-
parents << sexp
|
77
|
+
parents << sexp unless parents.include?(sexp)
|
62
78
|
elem = elem[1..-1]
|
63
79
|
end
|
64
|
-
each_parent_of(sym, elem)
|
80
|
+
each_parent_of(sym, elem) do |parent|
|
81
|
+
parents << parent unless parents.include?(parent)
|
82
|
+
end
|
65
83
|
end
|
66
|
-
|
67
|
-
parents.
|
84
|
+
end
|
85
|
+
parents.each { |parent| yield parent }
|
68
86
|
end
|
69
87
|
|
70
88
|
def each(sym, sexp)
|
71
89
|
yield sexp if sexp[0] == sym
|
72
|
-
sexp.each
|
90
|
+
sexp.each do |elem|
|
73
91
|
each(sym, elem) { |s| yield s } if Array === elem
|
74
|
-
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def whitespace?(token)
|
96
|
+
[:on_sp, :on_ignored_nl, :on_nl].include?(token.type)
|
75
97
|
end
|
76
98
|
end
|
77
99
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Rubocop
|
4
|
+
module Cop
|
5
|
+
class DefParentheses < Cop
|
6
|
+
ERROR_MESSAGE = ['Use def with parentheses when there are arguments.',
|
7
|
+
"Omit the parentheses in defs when the method " +
|
8
|
+
"doesn't accept any arguments."]
|
9
|
+
EMPTY_PARAMS = [:params, nil, nil, nil, nil, nil]
|
10
|
+
|
11
|
+
def inspect(file, source, tokens, sexp)
|
12
|
+
each(:def, sexp) do |def_sexp|
|
13
|
+
pos = def_sexp[1][-1]
|
14
|
+
case def_sexp[2][0]
|
15
|
+
when :params
|
16
|
+
if def_sexp[2] != EMPTY_PARAMS
|
17
|
+
add_offence(:convention, pos.lineno, ERROR_MESSAGE[0])
|
18
|
+
end
|
19
|
+
when :paren
|
20
|
+
if def_sexp[2][1] == EMPTY_PARAMS
|
21
|
+
method_name_ix = tokens.index { |t| t.pos == pos }
|
22
|
+
start = method_name_ix + 1
|
23
|
+
rparen_ix = start + tokens[start..-1].index { |t| t.text == ')' }
|
24
|
+
first_body_token = tokens[(rparen_ix + 1)..-1].find do |t|
|
25
|
+
not whitespace?(t)
|
26
|
+
end
|
27
|
+
if first_body_token.pos.lineno > pos.lineno
|
28
|
+
# Only report offence if there's a line break after
|
29
|
+
# the empty parens.
|
30
|
+
add_offence(:convention, pos.lineno, ERROR_MESSAGE[1])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
module Rubocop
|
2
4
|
module Cop
|
3
5
|
class EmptyLines < Cop
|
@@ -7,17 +9,16 @@ module Rubocop
|
|
7
9
|
each_parent_of(:def, sexp) do |parent|
|
8
10
|
defs = parent.select { |child| child[0] == :def }
|
9
11
|
identifier_of_first_def = defs[0][1]
|
10
|
-
current_row_ix = identifier_of_first_def[-1]
|
12
|
+
current_row_ix = identifier_of_first_def[-1].lineno - 1
|
11
13
|
# The first def doesn't need to have an empty line above it,
|
12
14
|
# so we iterate starting at index 1.
|
13
|
-
defs[1..-1].each
|
14
|
-
next_row_ix = child[1][-1]
|
15
|
+
defs[1..-1].each do |child|
|
16
|
+
next_row_ix = child[1][-1].lineno - 1
|
15
17
|
if source[current_row_ix..next_row_ix].grep(/^[ \t]*$/).empty?
|
16
|
-
add_offence(:convention, next_row_ix,
|
17
|
-
ERROR_MESSAGE)
|
18
|
+
add_offence(:convention, next_row_ix + 1, ERROR_MESSAGE)
|
18
19
|
end
|
19
20
|
current_row_ix = next_row_ix
|
20
|
-
|
21
|
+
end
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|