rubocop 0.6.1 → 0.7.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -266
  3. data/CHANGELOG.md +49 -7
  4. data/README.md +75 -2
  5. data/Rakefile +2 -2
  6. data/bin/rubocop +15 -10
  7. data/lib/rubocop.rb +19 -1
  8. data/lib/rubocop/cli.rb +113 -116
  9. data/lib/rubocop/config.rb +202 -0
  10. data/lib/rubocop/config_store.rb +37 -0
  11. data/lib/rubocop/cop/alias.rb +2 -5
  12. data/lib/rubocop/cop/align_parameters.rb +1 -1
  13. data/lib/rubocop/cop/array_literal.rb +43 -4
  14. data/lib/rubocop/cop/avoid_for.rb +2 -4
  15. data/lib/rubocop/cop/avoid_global_vars.rb +49 -0
  16. data/lib/rubocop/cop/block_comments.rb +17 -0
  17. data/lib/rubocop/cop/brace_after_percent.rb +9 -5
  18. data/lib/rubocop/cop/{indentation.rb → case_indentation.rb} +1 -1
  19. data/lib/rubocop/cop/class_methods.rb +20 -0
  20. data/lib/rubocop/cop/colon_method_call.rb +44 -0
  21. data/lib/rubocop/cop/cop.rb +30 -2
  22. data/lib/rubocop/cop/def_parentheses.rb +1 -1
  23. data/lib/rubocop/cop/empty_line_between_defs.rb +26 -0
  24. data/lib/rubocop/cop/empty_lines.rb +10 -13
  25. data/lib/rubocop/cop/eval.rb +22 -0
  26. data/lib/rubocop/cop/favor_join.rb +37 -0
  27. data/lib/rubocop/cop/grammar.rb +2 -2
  28. data/lib/rubocop/cop/hash_literal.rb +43 -4
  29. data/lib/rubocop/cop/hash_syntax.rb +2 -2
  30. data/lib/rubocop/cop/if_then_else.rb +1 -1
  31. data/lib/rubocop/cop/leading_comment_space.rb +20 -0
  32. data/lib/rubocop/cop/line_continuation.rb +18 -0
  33. data/lib/rubocop/cop/line_length.rb +1 -1
  34. data/lib/rubocop/cop/method_and_variable_snake_case.rb +7 -6
  35. data/lib/rubocop/cop/method_length.rb +4 -15
  36. data/lib/rubocop/cop/not.rb +15 -0
  37. data/lib/rubocop/cop/offence.rb +9 -0
  38. data/lib/rubocop/cop/semicolon.rb +74 -3
  39. data/lib/rubocop/cop/single_line_methods.rb +60 -0
  40. data/lib/rubocop/cop/space_after_control_keyword.rb +28 -0
  41. data/lib/rubocop/cop/surrounding_space.rb +48 -9
  42. data/lib/rubocop/cop/symbol_array.rb +29 -0
  43. data/lib/rubocop/cop/trivial_accessors.rb +103 -0
  44. data/lib/rubocop/cop/unless_else.rb +1 -1
  45. data/lib/rubocop/cop/variable_interpolation.rb +3 -2
  46. data/lib/rubocop/cop/word_array.rb +38 -0
  47. data/lib/rubocop/version.rb +1 -1
  48. data/rubocop.gemspec +11 -7
  49. data/spec/project_spec.rb +27 -0
  50. data/spec/rubocop/cli_spec.rb +549 -487
  51. data/spec/rubocop/config_spec.rb +399 -0
  52. data/spec/rubocop/config_store_spec.rb +66 -0
  53. data/spec/rubocop/cops/alias_spec.rb +7 -0
  54. data/spec/rubocop/cops/array_literal_spec.rb +8 -1
  55. data/spec/rubocop/cops/avoid_for_spec.rb +15 -1
  56. data/spec/rubocop/cops/avoid_global_vars.rb +32 -0
  57. data/spec/rubocop/cops/block_comments_spec.rb +29 -0
  58. data/spec/rubocop/cops/brace_after_percent_spec.rb +19 -13
  59. data/spec/rubocop/cops/{indentation_spec.rb → case_indentation_spec.rb} +2 -2
  60. data/spec/rubocop/cops/class_methods_spec.rb +49 -0
  61. data/spec/rubocop/cops/colon_method_call_spec.rb +47 -0
  62. data/spec/rubocop/cops/empty_line_between_defs_spec.rb +83 -0
  63. data/spec/rubocop/cops/empty_lines_spec.rb +6 -63
  64. data/spec/rubocop/cops/eval_spec.rb +36 -0
  65. data/spec/rubocop/cops/favor_join_spec.rb +39 -0
  66. data/spec/rubocop/cops/hash_literal_spec.rb +8 -1
  67. data/spec/rubocop/cops/leading_comment_space_spec.rb +60 -0
  68. data/spec/rubocop/cops/line_continuation_spec.rb +24 -0
  69. data/spec/rubocop/cops/line_length_spec.rb +1 -0
  70. data/spec/rubocop/cops/method_and_variable_snake_case_spec.rb +20 -0
  71. data/spec/rubocop/cops/method_length_spec.rb +2 -5
  72. data/spec/rubocop/cops/new_lambda_literal_spec.rb +2 -3
  73. data/spec/rubocop/cops/not_spec.rb +34 -0
  74. data/spec/rubocop/cops/offence_spec.rb +7 -0
  75. data/spec/rubocop/cops/semicolon_spec.rb +79 -4
  76. data/spec/rubocop/cops/single_line_methods_spec.rb +50 -0
  77. data/spec/rubocop/cops/space_after_control_keyword_spec.rb +28 -0
  78. data/spec/rubocop/cops/space_around_equals_in_default_parameter_spec.rb +11 -1
  79. data/spec/rubocop/cops/space_inside_hash_literal_braces_spec.rb +74 -0
  80. data/spec/rubocop/cops/symbol_array_spec.rb +25 -0
  81. data/spec/rubocop/cops/trivial_accessors_spec.rb +332 -0
  82. data/spec/rubocop/cops/variable_interpolation_spec.rb +10 -1
  83. data/spec/rubocop/cops/word_array_spec.rb +39 -0
  84. data/spec/spec_helper.rb +16 -9
  85. data/spec/support/file_helper.rb +21 -0
  86. data/spec/support/isolated_environment.rb +27 -0
  87. metadata +66 -6
@@ -10,30 +10,19 @@ module Rubocop
10
10
  def_lineno, end_lineno = def_and_end_lines(tokens, t_ix)
11
11
  length = calculate_length(def_lineno, end_lineno, source)
12
12
 
13
- if length > MethodLength.max
14
- message = sprintf(ERROR_MESSAGE, length, MethodLength.max)
13
+ max = MethodLength.config['Max']
14
+ if length > max
15
+ message = sprintf(ERROR_MESSAGE, length, max)
15
16
  add_offence(:convention, def_lineno, message)
16
17
  end
17
18
  end
18
19
  end
19
20
 
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
21
  private
33
22
 
34
23
  def calculate_length(def_lineno, end_lineno, source)
35
24
  lines = source[def_lineno..(end_lineno - 2)].reject(&:empty?)
36
- unless MethodLength.count_comments?
25
+ unless MethodLength.config['CountComments']
37
26
  lines = lines.reject { |line| line =~ /^\s*#/ }
38
27
  end
39
28
  lines.size
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class Not < Cop
6
+ ERROR_MESSAGE = 'Use ! instead of not.'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ each_keyword('not', tokens) do |t|
10
+ add_offence(:convention, t.pos.lineno, ERROR_MESSAGE)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -22,6 +22,15 @@ module Rubocop
22
22
  def encode_severity
23
23
  @severity.to_s[0].upcase
24
24
  end
25
+
26
+ def ==(other)
27
+ severity == other.severity and line_number == other.line_number and
28
+ message == other.message
29
+ end
30
+
31
+ def explode
32
+ [severity, line_number, message]
33
+ end
25
34
  end
26
35
  end
27
36
  end
@@ -6,13 +6,84 @@ module Rubocop
6
6
  ERROR_MESSAGE = 'Do not use semicolons to terminate expressions.'
7
7
 
8
8
  def inspect(file, source, tokens, sexp)
9
- tokens.each_index do |ix|
10
- t = tokens[ix]
9
+ @tokens = tokens
10
+ already_checked_line = nil
11
+ tokens.each_with_index do |t, ix|
11
12
  if t.type == :on_semicolon
12
- add_offence(:convention, t.pos.lineno, ERROR_MESSAGE)
13
+ next if t.pos.lineno == already_checked_line # fast-forward
14
+ token_1_ix = index_of_first_token_on_line(ix, t.pos.lineno)
15
+ if %w(def class module).include?(tokens[token_1_ix].text)
16
+ handle_exceptions_to_the_rule(token_1_ix)
17
+ if source[t.pos.lineno - 1] =~ /;\s*(#.*)?$/
18
+ # Semicolons at end of a lines are always reported.
19
+ add_offence(:convention, t.pos.lineno, ERROR_MESSAGE)
20
+ end
21
+ # When dealing with these one line definitions, we check
22
+ # the whole line at once. That's why we use the variable
23
+ # already_checked_line to know when to fast-forward past
24
+ # the current line.
25
+ already_checked_line = t.pos.lineno
26
+ else
27
+ add_offence(:convention, t.pos.lineno, ERROR_MESSAGE)
28
+ end
13
29
  end
14
30
  end
15
31
  end
32
+
33
+ def index_of_first_token_on_line(ix, lineno)
34
+ # Index of last token on the previous line
35
+ prev_line_ix =
36
+ @tokens[0...ix].rindex { |t| t.pos.lineno < lineno } || 0
37
+ # Index of first non-whitespace token on the current line.
38
+ prev_line_ix + @tokens[prev_line_ix..ix].index { |t| !whitespace?(t) }
39
+ end
40
+
41
+ def handle_exceptions_to_the_rule(token_1_ix)
42
+ # We only do further checking of the def case, which means
43
+ # that there are some cases of semicolon usage within
44
+ # non-empty one-line class or method definitions that we don't
45
+ # catch, but these should be rare.
46
+ if @tokens[token_1_ix].text == 'def'
47
+ state = :initial
48
+ @tokens[token_1_ix..-1].each do |t|
49
+ state = next_state(state, t) || state
50
+ break if t.text == 'end'
51
+ end
52
+ end
53
+ end
54
+
55
+ def next_state(state, token)
56
+ return nil if whitespace?(token) # no state change for whitespace
57
+
58
+ case state
59
+ when :initial
60
+ :def_kw if token.type == :on_kw
61
+ when :def_kw
62
+ :method_name if token.type == :on_ident
63
+ when :method_name
64
+ case token.type
65
+ when :on_lparen then :inside_param_list
66
+ when :on_semicolon then :method_body
67
+ end
68
+ when :inside_param_list
69
+ :right_after_param_list if token.type == :on_rparen
70
+ when :right_after_param_list
71
+ if token.type == :on_semicolon
72
+ unless Semicolon.config['AllowAfterParameterListInOneLineMethods']
73
+ add_offence(:convention, token.pos.lineno, ERROR_MESSAGE)
74
+ end
75
+ end
76
+ :method_body
77
+ when :method_body
78
+ :semicolon_used if token.type == :on_semicolon
79
+ when :semicolon_used
80
+ if token.text != 'end' ||
81
+ !Semicolon.config['AllowBeforeEndInOneLineMethods']
82
+ add_offence(:convention, token.pos.lineno, ERROR_MESSAGE)
83
+ end
84
+ :method_body
85
+ end
86
+ end
16
87
  end
17
88
  end
18
89
  end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class SingleLineMethods < Cop
6
+ ERROR_MESSAGE = 'Avoid single-line methods.'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ if SingleLineMethods.config['AllowIfMethodIsEmpty']
10
+ is_empty = empty_methods(sexp)
11
+ end
12
+
13
+ lineno_of_def = nil
14
+ possible_offence = false
15
+
16
+ tokens.each_with_index do |token, ix|
17
+ if possible_offence
18
+ if token.pos.lineno > lineno_of_def
19
+ possible_offence = false
20
+ elsif [token.type, token.text] == [:on_kw, 'end']
21
+ add_offence(:convention, lineno_of_def, ERROR_MESSAGE)
22
+ end
23
+ end
24
+
25
+ if [token.type, token.text] == [:on_kw, 'def']
26
+ lineno_of_def = token.pos.lineno
27
+ name_token = tokens[ix..-1].find do |t|
28
+ [:on_ident, :on_const].include?(t.type)
29
+ end
30
+ possible_offence =
31
+ if SingleLineMethods.config['AllowIfMethodIsEmpty']
32
+ !is_empty[name_token.pos]
33
+ else
34
+ true
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # Returns a hash mapping positions of method names to booleans
43
+ # saying whether or not the method is empty.
44
+ def empty_methods(sexp)
45
+ is_empty = {}
46
+ # Since def is a keyword, def: can confuse the editor. Hence
47
+ # Ruby 1.8 hash syntax is used here.
48
+ # rubocop:disable HashSyntax
49
+ { :def => [1, 3], :defs => [3, 5] }.each do |key, offsets|
50
+ each(key, sexp) do |d|
51
+ method_name_pos = d[offsets.first][-1]
52
+ is_empty[method_name_pos] =
53
+ (d[offsets.last] == [:bodystmt, [[:void_stmt]], nil, nil, nil])
54
+ end
55
+ end
56
+ is_empty
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class SpaceAfterControlKeyword < Cop
6
+ ERROR_MESSAGE = 'Use space after control keywords.'
7
+
8
+ KEYWORDS = %w(if elsif case when while until unless)
9
+
10
+ def inspect(file, source, tokens, sexp)
11
+ # we need to keep track of the previous token to
12
+ # avoid confusing symbols like :if with real keywords
13
+ prev = Token.new(0, :init, '')
14
+
15
+ tokens.each_cons(2) do |t1, t2|
16
+ if prev.type != :on_symbeg && t1.type == :on_kw &&
17
+ KEYWORDS.include?(t1.text) && t2.type != :on_sp
18
+ add_offence(:convention,
19
+ t1.pos.lineno,
20
+ ERROR_MESSAGE)
21
+ end
22
+
23
+ prev = t1
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -13,7 +13,7 @@ module Rubocop
13
13
  private
14
14
 
15
15
  def previous_non_space(tokens, ix)
16
- tokens[0...ix].reverse.find { |t| not whitespace?(t) }
16
+ tokens[0...ix].reverse.find { |t| !whitespace?(t) }
17
17
  end
18
18
 
19
19
  def ok_without_spaces?(grammar_path)
@@ -66,6 +66,9 @@ module Rubocop
66
66
  end
67
67
 
68
68
  def check_missing_space(tokens, ix, grammar_path)
69
+ # Checked by SpaceInsideHashLiteralBraces and not here.
70
+ return if grammar_path.last == :hash
71
+
69
72
  t = tokens[ix]
70
73
  case t.type
71
74
  when :on_lbrace
@@ -123,17 +126,53 @@ module Rubocop
123
126
  end
124
127
  end
125
128
 
129
+ class SpaceInsideHashLiteralBraces < Cop
130
+ include SurroundingSpace
131
+
132
+ def check_missing_space(tokens, ix, grammar_path)
133
+ if self.class.config['EnforcedStyleIsWithSpaces']
134
+ check_space(tokens, ix, grammar_path, 'missing') do |t|
135
+ !(whitespace?(t) || [:on_lbrace, :on_rbrace].include?(t.type))
136
+ end
137
+ end
138
+ end
139
+
140
+ def check_unwanted_space(tokens, ix)
141
+ unless self.class.config['EnforcedStyleIsWithSpaces']
142
+ grammar_path = @correlations[ix] or return
143
+ check_space(tokens, ix, grammar_path, 'detected') do |t|
144
+ whitespace?(t)
145
+ end
146
+ end
147
+ end
148
+
149
+ private
150
+
151
+ def check_space(tokens, ix, grammar_path, word)
152
+ if grammar_path[-1] == :hash
153
+ is_offence = case tokens[ix].type
154
+ when :on_lbrace then yield tokens[ix + 1]
155
+ when :on_rbrace then yield tokens[ix - 1]
156
+ else false
157
+ end
158
+ if is_offence
159
+ add_offence(:convention, tokens[ix].pos.lineno,
160
+ "Space inside hash literal braces #{word}.")
161
+ end
162
+ end
163
+ end
164
+ end
165
+
126
166
  class SpaceAroundEqualsInParameterDefault < Cop
127
167
  def inspect(file, source, tokens, sexp)
128
168
  each(:params, sexp) do |s|
129
- (s[2] || []).each do |param, value|
130
- value_pos = all_positions(value).first or next
131
- if param[-1].lineno == value_pos.lineno
132
- if value_pos.column - (param[-1].column + param[1].length) <= 2
133
- add_offence(:convention, param[-1].lineno,
134
- 'Surrounding space missing in default value ' +
135
- 'assignment.')
136
- end
169
+ (s[2] || []).each do |param, _|
170
+ param_pos = param.last
171
+ ix = tokens.index { |t| t.pos == param_pos }
172
+ unless whitespace?(tokens[ix + 1]) && whitespace?(tokens[ix + 3])
173
+ add_offence(:convention, param[-1].lineno,
174
+ 'Surrounding space missing in default value ' +
175
+ 'assignment.')
137
176
  end
138
177
  end
139
178
  end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class SymbolArray < Cop
6
+ ERROR_MESSAGE = 'Use %i or %I for array of symbols.'
7
+
8
+ def inspect(file, source, tokens, sexp)
9
+ # %i and %I were introduced in Ruby 2.0
10
+ unless RUBY_VERSION < '2.0.0'
11
+ each(:array, sexp) do |s|
12
+ array_elems = s[1]
13
+
14
+ # no need to check empty arrays
15
+ return unless array_elems && array_elems.size > 1
16
+
17
+ symbol_array = array_elems.all? { |e| e[0] == :symbol_literal }
18
+
19
+ if symbol_array
20
+ add_offence(:convention,
21
+ all_positions(s).first.lineno,
22
+ ERROR_MESSAGE)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,103 @@
1
+ # encoding: utf-8
2
+
3
+ module Rubocop
4
+ module Cop
5
+ class TrivialAccessors < Cop
6
+ READER_MESSAGE = 'Use attr_reader to define trivial reader methods.'
7
+ WRITER_MESSAGE = 'Use attr_writer to define trivial writer methods.'
8
+
9
+ def inspect(file, source, token, sexp)
10
+ each(:def, sexp) do |def_block|
11
+ find_trivial_accessors def_block
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ # Parse the sexp given, corresponding to a def method.
18
+ # Looking for a trivial reader/writer pattern
19
+ def find_trivial_accessors(sexp)
20
+ lineno = sexp[1][2].lineno
21
+ accessor_var = sexp[1][1]
22
+ if trivial_reader?(sexp, accessor_var)
23
+ add_offence(:convention, lineno, READER_MESSAGE)
24
+ elsif trivial_writer?(sexp, accessor_var)
25
+ add_offence(:convention, lineno, WRITER_MESSAGE)
26
+ end
27
+ end
28
+
29
+ # body statements that exclude a trivial accessor
30
+ NON_TRIVIAL_BODYSTMT = [:void_stmt, :unary, :binary,
31
+ :@float, :@int, :hash, :begin,
32
+ :yield0, :zsuper, :array]
33
+
34
+ # looking for a trivial reader method
35
+ def trivial_reader?(sexp, accessor_var)
36
+ if reader_shape?(sexp)
37
+ accessor_body = sexp[3][1][0][1][1]
38
+ accessor_body.slice!(0) if accessor_body[0] == '@'
39
+ accessor_var == accessor_body
40
+ end
41
+ end
42
+
43
+ # looking for a trivial writer method
44
+ def trivial_writer?(sexp, accessor_var)
45
+ if accessor_var[-1] == '=' &&
46
+ writer_shape?(sexp) &&
47
+ has_only_one_assignment?(sexp)
48
+ accessor_var.chop!
49
+ accessor_body = sexp[3][1][0][1][1][1]
50
+ accessor_body.slice!(0) if accessor_body[0] == '@'
51
+ unless sexp[3][1][0][0] == :vcall
52
+ body_purpose = sexp[3][1][0][2][0]
53
+ accessor_var == accessor_body && body_purpose == :var_ref
54
+ end
55
+ end
56
+ end
57
+
58
+ # return true if the sexp is a reader accessor, without params
59
+ # or with empty braces
60
+ def reader_shape?(sexp)
61
+ accessor_shape?(sexp) &&
62
+ (sexp[2][0] == :params || empty_params?(sexp[2]))
63
+ end
64
+
65
+ # return true if the sexp is a writer accessor, with a param
66
+ # and with or without braces
67
+ def writer_shape?(sexp)
68
+ accessor_shape?(sexp) && with_braces?(sexp[2])
69
+ end
70
+
71
+ # return true if the sexp has the common shape of an accessor
72
+ def accessor_shape?(sexp)
73
+ [:@ident, :@const].include?(sexp[1][0]) &&
74
+ sexp[3][0] == :bodystmt &&
75
+ !NON_TRIVIAL_BODYSTMT.include?(sexp[3][1][0][0])
76
+ end
77
+
78
+ # detect "def foo() ..." or
79
+ # "[:paren, [:params, nil, nil, nil, nil, nil, nil, nil]]"
80
+ def empty_params?(sexp)
81
+ sexp[0] == :paren &&
82
+ sexp[1][0] == :params &&
83
+ sexp[1][1..-1].reject { |x| !x }.empty?
84
+ end
85
+
86
+ # detect "def name= name" or "def name=(name)
87
+ def with_braces?(sexp)
88
+ (sexp[0] == :paren && sexp[1][0] == :params) ||
89
+ sexp[0] == :params
90
+ end
91
+
92
+ # return true if the sexp has only one assignment in the body
93
+ # false otherwise (maybe one or more function calls).
94
+ # why [3..-1]? because:
95
+ # [:bodystmt, [[:assign, [:var_field, [:var_ref ...] and no :vcall
96
+ # thus [:bodystmt, :assign, :var_ref, nil, nil, nil ...]
97
+ def has_only_one_assignment?(sexp)
98
+ sexp[3][1][1] == nil
99
+ end
100
+
101
+ end # TrivialAccessors
102
+ end # Cop
103
+ end # Rubocop