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
@@ -9,7 +9,7 @@ module Rubocop
9
9
  def inspect(file, source, tokens, sexp)
10
10
  unless source[0] =~ /#.*coding: (UTF|utf)-8/
11
11
  message = sprintf(ERROR_MESSAGE)
12
- add_offence(:convention, 0, 0, message)
12
+ add_offence(:convention, 1, message)
13
13
  end
14
14
  end
15
15
  end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class EndOfLine < Cop
6
+ ERROR_MESSAGE = 'Carriage return character detected.'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ source.each_with_index do |line, index|
10
+ if line =~ /\r$/
11
+ add_offence(:convention, index + 1, ERROR_MESSAGE)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,13 +1,61 @@
1
+ # encoding: utf-8
2
+
1
3
  module Rubocop
2
4
  module Cop
3
5
  class Grammar
4
6
  def initialize(tokens)
5
- @tokens_without_pos = tokens.map { |tok| tok[1..-1] }
7
+ @tokens_without_pos = tokens.map { |t| [t.type, t.text] }
8
+ process_embedded_expressions
9
+ @token_indexes = {}
10
+ @tokens_without_pos.each_with_index { |t, i|
11
+ @token_indexes[t] ||= []
12
+ @token_indexes[t] << i
13
+ }
6
14
  @ix = 0
7
15
  @table = {}
8
- token_positions = tokens.map { |tok| tok[0] }
16
+ token_positions = tokens.map { |t| [t.pos.lineno, t.pos.column] }
9
17
  @index_by_pos = Hash[*token_positions.each_with_index.to_a.flatten(1)]
10
- @special = { assign: '=' }
18
+ @special = {
19
+ assign: [:on_op, '='],
20
+ brace_block: [:on_lbrace, '{']
21
+ }
22
+ end
23
+
24
+ # The string "#{x}" will give the tokens
25
+ # [:on_tstring_beg, '"'], [:on_embexpr_beg, '#{'], [:on_ident, 'x'],
26
+ # [:on_rbrace, '}'], [:on_tstring_end, '"']
27
+ # which is not so good for us. We want to distinguish between a
28
+ # right brace that ends an embedded expression inside a string
29
+ # and an ordinary right brace. So we replace :on_rbrace with the
30
+ # made up :on_embexpr_end.
31
+ def process_embedded_expressions
32
+ state = :outside
33
+ brace_depth = 0
34
+ @tokens_without_pos.each_with_index do |(type, _), ix|
35
+ case state
36
+ when :outside
37
+ state = :inside_string if type == :on_tstring_beg
38
+ when :inside_string
39
+ case type
40
+ when :on_tstring_end
41
+ state = :outside
42
+ when :on_embexpr_beg
43
+ brace_depth = 1
44
+ state = :inside_expr
45
+ end
46
+ when :inside_expr
47
+ case type
48
+ when :on_lbrace
49
+ brace_depth += 1
50
+ when :on_rbrace
51
+ if brace_depth == 1
52
+ @tokens_without_pos[ix][0] = :on_embexpr_end
53
+ state = :inside_string
54
+ end
55
+ brace_depth -= 1
56
+ end
57
+ end
58
+ end
11
59
  end
12
60
 
13
61
  # Returns a hash mapping indexes in the token array to grammar
@@ -28,14 +76,15 @@ module Rubocop
28
76
  when /^@/
29
77
  # Leaves in the grammar have a corresponding token with a
30
78
  # position, which we search for and advance @ix.
31
- @ix = @index_by_pos[sexp[-1]]
79
+ @ix = @index_by_pos[[sexp[-1].lineno, sexp[-1].column]]
80
+ fail "#{sexp}\n#{@index_by_pos}" unless @ix
32
81
  @table[@ix] = path + [sexp[0]]
33
82
  @ix += 1
34
83
  when *@special.keys
35
84
  # Here we don't advance @ix because there may be other
36
85
  # tokens inbetween the current one and the one we get from
37
86
  # @special.
38
- find(path, sexp, [:on_op, @special[sexp[0]]])
87
+ find(path, sexp, @special[sexp[0]])
39
88
  when :block_var # "{ |...|" or "do |...|"
40
89
  @ix = find(path, sexp, [:on_op, '|']) + 1
41
90
  find(path, sexp, [:on_op, '|'])
@@ -44,7 +93,7 @@ module Rubocop
44
93
  # Compensate for reverse order of if modifier
45
94
  children = (sexp[0] == :if_mod) ? sexp.reverse : sexp
46
95
 
47
- children.each { |elem|
96
+ children.each do |elem|
48
97
  case elem
49
98
  when Array
50
99
  correlate(elem, path) # Dive deeper
@@ -56,7 +105,7 @@ module Rubocop
56
105
  find(path, [elem], [:on_op, elem.to_s.chomp('@')])
57
106
  end
58
107
  end
59
- }
108
+ end
60
109
  end
61
110
  @table
62
111
  end
@@ -64,11 +113,22 @@ module Rubocop
64
113
  private
65
114
 
66
115
  def find(path, sexp, token_to_find)
67
- offset = @tokens_without_pos[@ix..-1].index(token_to_find) or return
68
- ix = @ix + offset
116
+ indices = @token_indexes[token_to_find] or return
117
+ ix = indices.find { |i| i >= @ix } or return
69
118
  @table[ix] = path + [sexp[0]]
119
+ add_matching_rbrace(ix) if token_to_find == [:on_lbrace, '{']
70
120
  ix
71
121
  end
122
+
123
+ def add_matching_rbrace(ix)
124
+ brace_depth = 0
125
+ rbrace_offset = @tokens_without_pos[@ix..-1].index do |t|
126
+ brace_depth += 1 if t == [:on_lbrace, '{']
127
+ brace_depth -= 1 if t == [:on_rbrace, '}']
128
+ brace_depth == 0 && t == [:on_rbrace, '}']
129
+ end
130
+ @table[@ix + rbrace_offset] = @table[ix] if rbrace_offset
131
+ end
72
132
  end
73
133
  end
74
134
  end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class HashSyntax < Cop
6
+ ERROR_MESSAGE = 'Ruby 1.8 hash syntax detected'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ each(:assoclist_from_args, sexp) do |assoclist_from_args|
10
+ keys = assoclist_from_args[1].map { |assoc_new| assoc_new[1][0] }
11
+ # If at least one of the keys in the hash is neither a symbol (:a)
12
+ # nor a label (a:), we can't require the new syntax.
13
+ return if keys.find do |key|
14
+ not [:symbol_literal, :@label].include?(key)
15
+ end
16
+ end
17
+ each(:assoc_new, sexp) do |assoc_new|
18
+ if assoc_new[1][0] == :symbol_literal
19
+ add_offence(:convention, assoc_new[1][1][1][-1].lineno,
20
+ ERROR_MESSAGE)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class IfThenElse < Cop
6
+ ERROR_MESSAGE = {
7
+ multiline_if_then:
8
+ 'Never use then for multi-line if/unless.',
9
+ one_liner:
10
+ 'Favor the ternary operator (?:) over if/then/else/end constructs.',
11
+ semicolon:
12
+ 'Never use if x; Use the ternary operator instead.'
13
+ }
14
+
15
+ def inspect(file, source, tokens, sexp)
16
+ tokens.each_with_index do |t, ix|
17
+ if t.type == :on_kw && ['if', 'unless'].include?(t.text)
18
+ error = ERROR_MESSAGE[kind_of_if(tokens, ix + 1)]
19
+ add_offence(:convention, t.pos.lineno, error) if error
20
+ end
21
+ end
22
+ end
23
+
24
+ def kind_of_if(tokens, ix)
25
+ then_found = false
26
+ tokens[ix..-1].each do |t|
27
+ case t.type
28
+ when :on_kw
29
+ case t.text
30
+ when 'then' then then_found = true
31
+ when 'end' then return :one_liner
32
+ end
33
+ when :on_ignored_nl, :on_nl
34
+ break
35
+ when :on_semicolon
36
+ return :semicolon
37
+ when :on_comment
38
+ break if t.text =~ /\n/
39
+ when :on_sp
40
+ nil
41
+ else
42
+ then_found = false
43
+ end
44
+ end
45
+ then_found ? :multiline_if_then : nil
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Rubocop
2
4
  module Cop
3
5
  class Indentation < Cop
@@ -6,49 +8,36 @@ module Rubocop
6
8
  def inspect(file, source, tokens, sexp)
7
9
  case_tokens = find_keywords(tokens, 'case')
8
10
  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)
11
+ each_when(sexp) do |case_ix|
12
+ when_pos = when_tokens.shift.pos
13
+ if when_pos.column != case_tokens[case_ix].pos.column
14
+ add_offence(:convention, when_pos.lineno, ERROR_MESSAGE)
14
15
  end
15
- }
16
+ end
16
17
  end
17
18
 
18
19
  def find_keywords(tokens, keyword)
19
- indexes = tokens.each_index.find_all { |ix|
20
+ indexes = tokens.each_index.find_all do |ix|
20
21
  keyword?(tokens, ix, keyword)
21
- }
22
+ end
22
23
  tokens.values_at(*indexes)
23
24
  end
24
25
 
25
26
  def keyword?(tokens, ix, keyword)
26
- tokens[ix][1..-1] == [:on_kw, keyword] &&
27
- tokens[ix - 1][1] != :on_symbeg
27
+ [tokens[ix].type, tokens[ix].text] == [:on_kw, keyword] &&
28
+ tokens[ix - 1].type != :on_symbeg
28
29
  end
29
30
 
30
31
  # Does a depth first search for :when, yielding the index of the
31
32
  # corresponding :case for each one.
32
33
  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
34
+ if sexp[0] == :case
35
+ @total_case_ix = (@total_case_ix || -1) + 1
36
+ each_when(sexp[2], @total_case_ix, &block)
43
37
  else
44
- sexp.grep(Array).each { |s| case_ix = each_when(s, case_ix, &block) }
38
+ yield case_ix if sexp[0] == :when
39
+ sexp.grep(Array).each { |s| each_when(s, case_ix, &block) }
45
40
  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
41
  end
53
42
  end
54
43
  end
@@ -6,11 +6,11 @@ module Rubocop
6
6
  ERROR_MESSAGE = 'Line is too long. [%d/%d]'
7
7
  MAX_LINE_LENGTH = 79
8
8
 
9
- def inspect(file, source)
9
+ def inspect(file, source, tokens, sexp)
10
10
  source.each_with_index do |line, index|
11
11
  if line.length > MAX_LINE_LENGTH
12
12
  message = sprintf(ERROR_MESSAGE, line.length, MAX_LINE_LENGTH)
13
- add_offence(:convention, index, line, message)
13
+ add_offence(:convention, index + 1, message)
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class NumericLiterals < Cop
6
+ ERROR_MESSAGE = 'Add underscores to large numeric literals to ' +
7
+ 'improve their readability.'
8
+
9
+ def inspect(file, source, tokens, sexp)
10
+ tokens.each do |t|
11
+ if [:on_int, :on_float].include?(t.type) &&
12
+ t.text.split('.').grep(/\d{6}/).any?
13
+ add_offence(:convention, t.pos.lineno, ERROR_MESSAGE)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -3,14 +3,13 @@
3
3
  module Rubocop
4
4
  module Cop
5
5
  class Offence
6
- attr_accessor :severity, :line_number, :line, :message
6
+ attr_accessor :severity, :line_number, :message
7
7
 
8
8
  SEVERITIES = [:refactor, :convention, :warning, :error, :fatal]
9
9
 
10
- def initialize(severity, line_number, line, message)
10
+ def initialize(severity, line_number, message)
11
11
  @severity = severity
12
12
  @line_number = line_number
13
- @line = line
14
13
  @message = message
15
14
  end
16
15
 
@@ -1,23 +1,24 @@
1
+ # encoding: utf-8
2
+
1
3
  module Rubocop
2
4
  module Cop
3
5
  class SpaceAfterCommaEtc < Cop
4
6
  ERROR_MESSAGE = 'Space missing after %s.'
5
7
 
6
8
  def inspect(file, source, tokens, sexp)
7
- tokens.each_index { |ix|
8
- pos, name, text = tokens[ix]
9
- kind = case name
9
+ tokens.each_index do |ix|
10
+ t = tokens[ix]
11
+ kind = case t.type
10
12
  when :on_comma then 'comma'
11
13
  when :on_label then 'colon'
12
- when :on_op then 'colon' if text == ':'
14
+ when :on_op then 'colon' if t.text == ':'
13
15
  when :on_semicolon then 'semicolon'
14
16
  end
15
- if kind and not [:on_sp, :on_ignored_nl].include?(tokens[ix + 1][1])
16
- index = pos[0] - 1
17
- add_offence(:convention, index, source[index],
18
- ERROR_MESSAGE % kind)
17
+ if kind and not [:on_sp,
18
+ :on_ignored_nl].include?(tokens[ix + 1].type)
19
+ add_offence(:convention, t.pos.lineno, ERROR_MESSAGE % kind)
19
20
  end
20
- }
21
+ end
21
22
  end
22
23
  end
23
24
  end
@@ -1,32 +1,85 @@
1
+ # encoding: utf-8
2
+
1
3
  require_relative 'grammar'
2
4
 
3
5
  module Rubocop
4
6
  module Cop
5
7
  class SurroundingSpace < Cop
6
- ERROR_MESSAGE = 'Surrounding space missing for operator'
8
+ ERROR_MESSAGE = 'Surrounding space missing for '
7
9
 
8
10
  def inspect(file, source, tokens, sexp)
9
- Grammar.new(tokens).correlate(sexp).sort.each { |ix, grammar_path|
10
- pos, name, text = tokens[ix]
11
- if name == :on_op
11
+ Grammar.new(tokens).correlate(sexp).sort.each do |ix, grammar_path|
12
+ t = tokens[ix]
13
+ case t.type
14
+ when :on_op
12
15
  unless surrounded_by_whitespace?(tokens[ix - 1, 3])
13
16
  unless ok_without_spaces?(grammar_path)
14
- index = pos[0] - 1
15
- add_offence(:convention, index, source[index],
16
- ERROR_MESSAGE + " '#{text}'.")
17
+ add_offence(:convention, t.pos.lineno,
18
+ ERROR_MESSAGE + "operator '#{t.text}'.")
17
19
  end
18
20
  end
21
+ when :on_lbrace
22
+ unless surrounded_by_whitespace?(tokens[ix - 1, 3])
23
+ add_offence(:convention, t.pos.lineno, ERROR_MESSAGE + "'{'.")
24
+ end
25
+ when :on_rbrace
26
+ unless whitespace?(tokens[ix - 1])
27
+ add_offence(:convention, t.pos.lineno,
28
+ "Space missing to the left of '}'.")
29
+ end
30
+ end
31
+ end
32
+ tokens.each_index do |ix|
33
+ t = tokens[ix]
34
+ prev, nxt = tokens.values_at(ix - 1, ix + 1)
35
+ offence_detected = case t.type
36
+ when :on_lbracket, :on_lparen
37
+ nxt.type == :on_sp
38
+ when :on_rbracket, :on_rparen
39
+ if prev.type == :on_sp
40
+ prev_ns = previous_non_space(tokens, ix)
41
+ prev_ns && tokens_on_same_row?(prev_ns,
42
+ tokens[ix]) &&
43
+ # Avoid double repoting of [ ] and ( )
44
+ prev_ns.type != :on_lbracket &&
45
+ prev_ns.type != :on_lparen
46
+ end
47
+ when :on_op
48
+ t.text == '**' &&
49
+ (whitespace?(prev) || whitespace?(nxt))
50
+ end
51
+ if offence_detected
52
+ kind = case t.type
53
+ when :on_lparen, :on_rparen
54
+ 'inside parentheses'
55
+ when :on_lbracket, :on_rbracket
56
+ 'inside square brackets'
57
+ when :on_op
58
+ "around operator #{t.text}"
59
+ end
60
+ add_offence(:convention, t.pos.lineno, "Space #{kind} detected.")
19
61
  end
20
- }
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def tokens_on_same_row?(t1, t2)
68
+ t1.pos.lineno == t2.pos.lineno
69
+ end
70
+
71
+ def previous_non_space(tokens, ix)
72
+ (ix - 1).downto(0) do |i|
73
+ t = tokens[i]
74
+ return t unless whitespace?(t)
75
+ end
76
+ nil
21
77
  end
22
78
 
23
79
  def ok_without_spaces?(grammar_path)
24
- grandparent, parent, child = grammar_path.values_at(-3, -2, -1)
80
+ parent, child = grammar_path.values_at(-2, -1)
25
81
  return true if [:unary, :symbol, :defs, :def, :call].include?(parent)
26
- return true if [:rest_param, :blockarg, :block_var, :args_add_star,
27
- :args_add_block, :const_path_ref, :dot2,
28
- :dot3].include?(child)
29
- return true if grandparent == :unary && parent == :vcall
82
+ return true if [:**, :block_var].include?(child)
30
83
  return true if parent == :command_call && child == :'::'
31
84
  false
32
85
  end
@@ -35,10 +88,6 @@ module Rubocop
35
88
  left, _, right = nearby_tokens
36
89
  whitespace?(left) && whitespace?(right)
37
90
  end
38
-
39
- def whitespace?(token)
40
- [:on_sp, :on_ignored_nl, :on_nl].include?(token[1])
41
- end
42
91
  end
43
92
  end
44
93
  end