rubocop 0.5.0 → 0.6.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 (57) hide show
  1. data/.rubocop.yml +59 -1
  2. data/CHANGELOG.md +38 -0
  3. data/Gemfile +1 -10
  4. data/README.md +28 -1
  5. data/Rakefile +1 -15
  6. data/lib/rubocop.rb +14 -0
  7. data/lib/rubocop/cli.rb +70 -7
  8. data/lib/rubocop/cop/alias.rb +5 -8
  9. data/lib/rubocop/cop/array_literal.rb +22 -0
  10. data/lib/rubocop/cop/ascii_identifiers_and_comments.rb +18 -0
  11. data/lib/rubocop/cop/avoid_perlisms.rb +19 -28
  12. data/lib/rubocop/cop/brace_after_percent.rb +28 -0
  13. data/lib/rubocop/cop/cop.rb +15 -3
  14. data/lib/rubocop/cop/encoding.rb +1 -1
  15. data/lib/rubocop/cop/ensure_return.rb +36 -0
  16. data/lib/rubocop/cop/favor_percent_r.rb +19 -0
  17. data/lib/rubocop/cop/favor_sprintf.rb +2 -10
  18. data/lib/rubocop/cop/grammar.rb +6 -3
  19. data/lib/rubocop/cop/handle_exceptions.rb +21 -0
  20. data/lib/rubocop/cop/hash_literal.rb +22 -0
  21. data/lib/rubocop/cop/method_length.rb +66 -0
  22. data/lib/rubocop/cop/op_method.rb +23 -0
  23. data/lib/rubocop/cop/percent_literals.rb +25 -0
  24. data/lib/rubocop/cop/percent_r.rb +19 -0
  25. data/lib/rubocop/cop/reduce_arguments.rb +67 -0
  26. data/lib/rubocop/cop/rescue_exception.rb +39 -0
  27. data/lib/rubocop/cop/rescue_modifier.rb +20 -0
  28. data/lib/rubocop/cop/symbol_snake_case.rb +5 -5
  29. data/lib/rubocop/cop/syntax.rb +13 -2
  30. data/lib/rubocop/version.rb +3 -1
  31. data/rubocop.gemspec +36 -169
  32. data/spec/rubocop/cli_spec.rb +146 -15
  33. data/spec/rubocop/cops/alias_spec.rb +10 -1
  34. data/spec/rubocop/cops/array_literal_spec.rb +29 -0
  35. data/spec/rubocop/cops/ascii_identifiers_and_comments_spec.rb +38 -0
  36. data/spec/rubocop/cops/avoid_perlisms_spec.rb +12 -0
  37. data/spec/rubocop/cops/brace_after_percent_spec.rb +27 -0
  38. data/spec/rubocop/cops/encoding_spec.rb +2 -2
  39. data/spec/rubocop/cops/ensure_return_spec.rb +37 -0
  40. data/spec/rubocop/cops/favor_percent_r.rb +29 -0
  41. data/spec/rubocop/cops/favor_sprintf_spec.rb +8 -1
  42. data/spec/rubocop/cops/grammar_spec.rb +54 -40
  43. data/spec/rubocop/cops/handle_exceptions_spec.rb +36 -0
  44. data/spec/rubocop/cops/hash_literal_spec.rb +29 -0
  45. data/spec/rubocop/cops/method_length_spec.rb +150 -0
  46. data/spec/rubocop/cops/op_method_spec.rb +58 -0
  47. data/spec/rubocop/cops/percent_literals_spec.rb +47 -0
  48. data/spec/rubocop/cops/percent_r_spec.rb +29 -0
  49. data/spec/rubocop/cops/reduce_arguments_spec.rb +57 -0
  50. data/spec/rubocop/cops/rescue_exception_spec.rb +73 -0
  51. data/spec/rubocop/cops/rescue_modifier.rb +40 -0
  52. data/spec/rubocop/cops/space_around_operators_spec.rb +7 -0
  53. data/spec/rubocop/cops/symbol_snake_case_spec.rb +19 -7
  54. data/spec/rubocop/cops/tab_spec.rb +1 -1
  55. metadata +131 -50
  56. data/Gemfile.lock +0 -41
  57. data/VERSION +0 -1
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class BraceAfterPercent < Cop
6
+ ERROR_MESSAGE = 'Prefer () as delimiters for all % literals.'
7
+ LITERALS = {
8
+ on_tstring_beg: '%q',
9
+ on_words_beg: '%W',
10
+ on_qwords_beg: '%w',
11
+ on_regexp_beg: '%r',
12
+ on_symbeg: '%s',
13
+ on_backtick: '%x'
14
+ }
15
+
16
+ def inspect(file, source, tokens, sexp)
17
+ tokens.each_index do |ix|
18
+ t = tokens[ix]
19
+ token = LITERALS[t.type]
20
+ if token && t.text.downcase.start_with?(token) && t.text[2] != '('
21
+ add_offence(:convention, t.pos.lineno,
22
+ ERROR_MESSAGE)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -32,7 +32,7 @@ module Rubocop
32
32
 
33
33
  class Cop
34
34
  attr_accessor :offences
35
- attr_writer :correlations
35
+ attr_writer :correlations, :disabled_lines
36
36
 
37
37
  @all = []
38
38
  @config = {}
@@ -55,7 +55,9 @@ module Rubocop
55
55
  end
56
56
 
57
57
  def add_offence(severity, line_number, message)
58
- @offences << Offence.new(severity, line_number, message)
58
+ unless @disabled_lines && @disabled_lines.include?(line_number)
59
+ @offences << Offence.new(severity, line_number, message)
60
+ end
59
61
  end
60
62
 
61
63
  private
@@ -83,13 +85,23 @@ module Rubocop
83
85
  end
84
86
  end
85
87
 
88
+ def find_all(sym, sexp)
89
+ result = []
90
+ each(sym, sexp) { |s| result << s }
91
+ result
92
+ end
93
+
94
+ def find_first(sym, sexp)
95
+ find_all(sym, sexp).first
96
+ end
97
+
86
98
  def whitespace?(token)
87
99
  [:on_sp, :on_ignored_nl, :on_nl].include?(token.type)
88
100
  end
89
101
 
90
102
  def all_positions(sexp)
91
103
  return [sexp[2]] if sexp[0] =~ /^@/
92
- sexp.grep(Array).reduce([]) { |memo, s| memo + all_positions(s) }
104
+ sexp.grep(Array).reduce([]) { |a, e| a + all_positions(e) }
93
105
  end
94
106
  end
95
107
  end
@@ -3,7 +3,7 @@
3
3
  module Rubocop
4
4
  module Cop
5
5
  class Encoding < Cop
6
- ERROR_MESSAGE = 'Missing encoding comment.'
6
+ ERROR_MESSAGE = 'Missing utf-8 encoding comment.'
7
7
 
8
8
  def inspect(file, source, tokens, sexp)
9
9
  unless RUBY_VERSION >= '2.0.0'
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class EnsureReturn < Cop
6
+ ERROR_MESSAGE = 'Never return from an ensure block.'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ in_ensure = false
10
+ ensure_col = nil
11
+
12
+ tokens.each_index do |ix|
13
+ t = tokens[ix]
14
+ if ensure_start?(t)
15
+ in_ensure = true
16
+ ensure_col = t.pos.column
17
+ elsif ensure_end?(t, ensure_col)
18
+ in_ensure = false
19
+ elsif in_ensure && t.type == :on_kw && t.text == 'return'
20
+ add_offence(:warning, t.pos.lineno, ERROR_MESSAGE)
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def ensure_start?(t)
28
+ t.type == :on_kw && t.text == 'ensure'
29
+ end
30
+
31
+ def ensure_end?(t, column)
32
+ t.type == :on_kw && t.text == 'end' && t.pos.column == column
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class FavorPercentR < Cop
6
+ ERROR_MESSAGE = 'Use %r for regular expressions matching more ' +
7
+ "than one '/' character."
8
+
9
+ def inspect(file, source, tokens, sexp)
10
+ tokens.each_cons(2) do |t1, t2|
11
+ if t1.type == :on_regexp_beg && t1.text == '/' &&
12
+ t2.text.scan(/\//).size > 1
13
+ add_offence(:convention, t1.pos.lineno, ERROR_MESSAGE)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -14,16 +14,8 @@ module Rubocop
14
14
  # we care only about the % operator
15
15
  next unless matching?(operator, op1, op2)
16
16
 
17
- # FIXME implement reliable lineno extraction
18
- # we either have some string literal
19
- if op1[0] == :string_literal
20
- lineno_struct = s[1][1][1][2]
21
- lineno = lineno_struct.lineno if lineno_struct.respond_to?(:lineno)
22
- else
23
- # or some identifier
24
- lineno_struct = s[1][1][2]
25
- lineno = lineno_struct.lineno if lineno_struct.respond_to?(:lineno)
26
- end
17
+ pos = all_positions(s[1]).first
18
+ lineno = pos.lineno if pos
27
19
 
28
20
  add_offence(
29
21
  :convention,
@@ -16,8 +16,9 @@ module Rubocop
16
16
  token_positions = tokens.map { |t| [t.pos.lineno, t.pos.column] }
17
17
  @index_by_pos = Hash[*token_positions.each_with_index.to_a.flatten(1)]
18
18
  @special = {
19
- assign: [:on_op, '='],
20
- brace_block: [:on_lbrace, '{']
19
+ assign: [[:on_op, '=']],
20
+ brace_block: [[:on_lbrace, '{']],
21
+ ifop: [[:on_op, '?'], [:on_op, ':']]
21
22
  }
22
23
  end
23
24
 
@@ -84,7 +85,9 @@ module Rubocop
84
85
  # Here we don't advance @ix because there may be other
85
86
  # tokens inbetween the current one and the one we get from
86
87
  # @special.
87
- find(path, sexp, @special[sexp[0]])
88
+ @special[sexp[0]].each do |token_to_find|
89
+ find(path, sexp, token_to_find)
90
+ end
88
91
  when :block_var # "{ |...|" or "do |...|"
89
92
  @ix = find(path, sexp, [:on_op, '|']) + 1
90
93
  find(path, sexp, [:on_op, '|'])
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class HandleExceptions < Cop
6
+ ERROR_MESSAGE = 'Do not suppress exceptions.'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ each(:begin, sexp) do |s|
10
+ each(:rescue, s) do |rs|
11
+ if rs[3] == [[:void_stmt]]
12
+ add_offence(:warning,
13
+ all_positions(s)[-1].lineno + 1,
14
+ ERROR_MESSAGE)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class HashLiteral < Cop
6
+ ERROR_MESSAGE = 'Use hash literal {} instead of Hash.new.'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ each(:method_add_arg, sexp) do |s|
10
+ potential_class = s[1][1][1]
11
+
12
+ if potential_class[1] == 'Hash' && s[1][3][1] == 'new' &&
13
+ s[2] == [:arg_paren, nil]
14
+ add_offence(:convention,
15
+ potential_class[2].lineno,
16
+ ERROR_MESSAGE)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class MethodLength < Cop
6
+ ERROR_MESSAGE = 'Method has too many lines. [%d/%d]'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ def_token_indices(tokens, source).each do |t_ix|
10
+ def_lineno, end_lineno = def_and_end_lines(tokens, t_ix)
11
+ length = calculate_length(def_lineno, end_lineno, source)
12
+
13
+ if length > MethodLength.max
14
+ message = sprintf(ERROR_MESSAGE, length, MethodLength.max)
15
+ add_offence(:convention, def_lineno, message)
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.max
21
+ MethodLength.config ? MethodLength.config['Max'] || 10 : 10
22
+ end
23
+
24
+ def self.count_comments?
25
+ if MethodLength.config
26
+ MethodLength.config['CountComments'] || false
27
+ else
28
+ false
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def calculate_length(def_lineno, end_lineno, source)
35
+ lines = source[def_lineno..(end_lineno - 2)].reject(&:empty?)
36
+ unless MethodLength.count_comments?
37
+ lines = lines.reject { |line| line =~ /^\s*#/ }
38
+ end
39
+ lines.size
40
+ end
41
+
42
+ def def_token_indices(tokens, source)
43
+ tokens.each_index.select do |ix|
44
+ t = tokens[ix]
45
+
46
+ # Need to check:
47
+ # 1. if the previous character is a ':' to prevent matching ':def'
48
+ # 2. if the method is a one line, which we will ignore
49
+ [t.type, t.text] == [:on_kw, 'def'] &&
50
+ source[t.pos.lineno - 1][t.pos.column - 1] != ':' &&
51
+ source[t.pos.lineno - 1] !~ /^\s*def.*(?:\(.*\)|;).*end\s*$/
52
+ end
53
+ end
54
+
55
+ # Find the matching 'end' based on the indentation of 'def'
56
+ # Fall back to last token if indentation cannot be matched
57
+ def def_and_end_lines(tokens, t_ix)
58
+ t1 = tokens[t_ix]
59
+ t2 = tokens[(t_ix + 1)..-1].find(-> { tokens[-1] }) do |t|
60
+ [t1.pos.column, t.type, t.text] == [t.pos.column, :on_kw, 'end']
61
+ end
62
+ [t1.pos.lineno, t2.pos.lineno]
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class OpMethod < Cop
6
+ ERROR_MESSAGE = 'When defining the %s operator, name its argument other.'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ each(:def, sexp) do |s|
10
+ if s[1][0] == :@op && !%w([] []= <<).include?(s[1][1])
11
+ param = s[2][1][1][0]
12
+
13
+ unless param[1] == 'other'
14
+ add_offence(:convention,
15
+ param[2].lineno,
16
+ sprintf(ERROR_MESSAGE, s[1][1]))
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class PercentLiterals < Cop
6
+ ERROR_MESSAGE = 'The use of %s is discouraged.'
7
+ BAD_LITERALS = {
8
+ on_tstring_beg: '%q',
9
+ on_symbeg: '%s',
10
+ on_backtick: '%x'
11
+ }
12
+
13
+ def inspect(file, source, tokens, sexp)
14
+ tokens.each_index do |ix|
15
+ t = tokens[ix]
16
+ bad_token = BAD_LITERALS[t.type]
17
+ if bad_token && t.text.downcase.start_with?(bad_token)
18
+ add_offence(:convention, t.pos.lineno,
19
+ sprintf(ERROR_MESSAGE, t.text[0, 2]))
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class PercentR < Cop
6
+ ERROR_MESSAGE = 'Use %r only for regular expressions matching more ' +
7
+ "than one '/' character."
8
+
9
+ def inspect(file, source, tokens, sexp)
10
+ tokens.each_cons(2) do |t1, t2|
11
+ if t1.type == :on_regexp_beg && t1.text =~ /^%r/ &&
12
+ t2.text !~ %r(/.*/)
13
+ add_offence(:convention, t1.pos.lineno, ERROR_MESSAGE)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class ReduceArguments < Cop
6
+ ERROR_MESSAGE = 'Name reduce arguments |a, e| (accumulator, element)'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ find_reduce_blocks(tokens).each do |reduce_block|
10
+ l_ix, r_ix = reduce_block
11
+ unless arguments_named_properly?(l_ix, r_ix, tokens)
12
+ add_offence(:convention, tokens[l_ix].pos.lineno, ERROR_MESSAGE)
13
+ end
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def arguments_named_properly?(l_ix, r_ix, tokens)
20
+ a, e = false, false
21
+ tokens[l_ix..r_ix].each do |t|
22
+ if a == true && [t.type, t.text] == [:on_ident, 'e']
23
+ e = true
24
+ elsif [t.type, t.text] == [:on_ident, 'a']
25
+ a = true
26
+ end
27
+ end
28
+ a && e
29
+ end
30
+
31
+ def find_reduce_blocks(tokens)
32
+ blocks = []
33
+ tokens.each_with_index do |t, ix|
34
+ if [t.type, t.text] == [:on_ident, 'reduce']
35
+ # Make sure we didn't select a :reduce symbol
36
+ next if tokens[ix - 1].text == ':'
37
+
38
+ block = find_brace_block(tokens, ix)
39
+ blocks << block if block
40
+ end
41
+ end
42
+ blocks
43
+ end
44
+
45
+ def find_brace_block(tokens, reduce_ix)
46
+ stack = []
47
+ block = false
48
+
49
+ # When we find the braces we need to add reduce_ix in order to
50
+ # find the real tokens index, since we're looping through a subset
51
+ tokens[reduce_ix..-1].each_with_index do |t, ix|
52
+ break if t.pos.lineno != tokens[reduce_ix].pos.lineno
53
+ if [:on_lbrace, :on_tlambeg].include? t.type
54
+ stack.push ix + reduce_ix
55
+ elsif t.type == :on_rbrace
56
+ left_ix = stack.pop
57
+ ix += reduce_ix
58
+ block = [left_ix, ix] if (stack.empty? &&
59
+ tokens[left_ix].type != :on_tlambeg)
60
+ end
61
+ end
62
+
63
+ block
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class RescueException < Cop
6
+ ERROR_MESSAGE = 'Avoid rescuing the Exception class.'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ each(:rescue, sexp) do |s|
10
+ # TODO Improve handling of rescue One, Two => e
11
+ if valid_case?(s)
12
+ target_class = s[1][0][1][1]
13
+
14
+ lineno = s[1][0][1][2].lineno
15
+
16
+ add_offence(:warning,
17
+ lineno,
18
+ ERROR_MESSAGE) if target_class == 'Exception'
19
+ end
20
+ end
21
+ end
22
+
23
+ def valid_case?(s)
24
+ if s[1].nil?
25
+ # rescue with no args
26
+ false
27
+ elsif s[1][0] == :mrhs_new_from_args
28
+ # rescue One, Two => e
29
+ false
30
+ elsif s[1][0][0] == :const_path_ref
31
+ # rescue Module::Class
32
+ false
33
+ else
34
+ true
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end