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.

Files changed (50) hide show
  1. data/.rbenv-version +1 -0
  2. data/.travis.yml +4 -0
  3. data/Gemfile +8 -8
  4. data/README.md +17 -1
  5. data/VERSION +1 -1
  6. data/bin/rubocop +1 -1
  7. data/features/step_definitions/rubocop_steps.rb +1 -0
  8. data/features/support/env.rb +2 -0
  9. data/lib/rubocop.rb +6 -0
  10. data/lib/rubocop/cli.rb +37 -17
  11. data/lib/rubocop/cop/align_parameters.rb +112 -0
  12. data/lib/rubocop/cop/cop.rb +43 -21
  13. data/lib/rubocop/cop/def_parentheses.rb +38 -0
  14. data/lib/rubocop/cop/empty_lines.rb +7 -6
  15. data/lib/rubocop/cop/encoding.rb +1 -1
  16. data/lib/rubocop/cop/end_of_line.rb +17 -0
  17. data/lib/rubocop/cop/grammar.rb +69 -9
  18. data/lib/rubocop/cop/hash_syntax.rb +26 -0
  19. data/lib/rubocop/cop/if_then_else.rb +49 -0
  20. data/lib/rubocop/cop/indentation.rb +16 -27
  21. data/lib/rubocop/cop/line_length.rb +2 -2
  22. data/lib/rubocop/cop/numeric_literals.rb +19 -0
  23. data/lib/rubocop/cop/offence.rb +2 -3
  24. data/lib/rubocop/cop/space_after_comma_etc.rb +10 -9
  25. data/lib/rubocop/cop/surrounding_space.rb +66 -17
  26. data/lib/rubocop/cop/tab.rb +2 -2
  27. data/lib/rubocop/cop/trailing_whitespace.rb +2 -2
  28. data/lib/rubocop/report/emacs_style.rb +4 -3
  29. data/rubocop.gemspec +16 -2
  30. data/spec/rubocop/cli_spec.rb +20 -5
  31. data/spec/rubocop/cops/align_parameters_spec.rb +201 -0
  32. data/spec/rubocop/cops/cop_spec.rb +4 -2
  33. data/spec/rubocop/cops/def_parentheses_spec.rb +48 -0
  34. data/spec/rubocop/cops/empty_lines_spec.rb +9 -8
  35. data/spec/rubocop/cops/end_of_line_spec.rb +17 -0
  36. data/spec/rubocop/cops/grammar_spec.rb +51 -11
  37. data/spec/rubocop/cops/hash_syntax_spec.rb +44 -0
  38. data/spec/rubocop/cops/if_then_else_spec.rb +74 -0
  39. data/spec/rubocop/cops/indentation_spec.rb +29 -4
  40. data/spec/rubocop/cops/line_length_spec.rb +4 -2
  41. data/spec/rubocop/cops/numeric_literals_spec.rb +49 -0
  42. data/spec/rubocop/cops/offence_spec.rb +4 -3
  43. data/spec/rubocop/cops/space_after_comma_etc_spec.rb +7 -5
  44. data/spec/rubocop/cops/surrounding_space_spec.rb +89 -26
  45. data/spec/rubocop/cops/tab_spec.rb +4 -2
  46. data/spec/rubocop/cops/trailing_whitespace_spec.rb +5 -3
  47. data/spec/rubocop/reports/emacs_style_spec.rb +4 -2
  48. data/spec/rubocop/reports/report_spec.rb +3 -1
  49. data/spec/spec_helper.rb +9 -1
  50. metadata +17 -3
@@ -0,0 +1 @@
1
+ 1.9.3-p327
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ script: bundle exec rspec
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
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 "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"
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
1
+ 0.2.0
@@ -12,5 +12,5 @@ time = Benchmark.realtime do
12
12
  result = cli.run
13
13
  end
14
14
 
15
- puts "Finished in #{time} seconds"
15
+ puts "Finished in #{time} seconds" if $options[:verbose]
16
16
  exit result
@@ -0,0 +1 @@
1
+ # encoding: utf-8
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'bundler'
2
4
  begin
3
5
  Bundler.setup(:default, :development)
@@ -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'
@@ -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 = { :mode => :default }
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).reject { |f| File.directory?(f) }.each do |file|
31
- report = Report.create(file, options[:mode])
32
- source = File.readlines(file).map { |line|
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, :invalid => :replace, :replace => '')
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.inspect_source(file, source)
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
- if glob = args.detect { |arg| arg =~ /\*/ }
69
- Dir[glob]
70
- else
71
- args
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
@@ -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 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)
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 { |elem|
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) { |parent| parents << parent }
80
+ each_parent_of(sym, elem) do |parent|
81
+ parents << parent unless parents.include?(parent)
82
+ end
65
83
  end
66
- }
67
- parents.uniq.each { |parent| yield parent }
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 { |elem|
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][0] - 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 { |child|
14
- next_row_ix = child[1][-1][0] - 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, source[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