rubocop 0.7.2 → 0.8.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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -1
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +4 -8
  5. data/bin/rubocop +2 -2
  6. data/config/default.yml +8 -0
  7. data/config/enabled.yml +21 -24
  8. data/lib/rubocop.rb +9 -7
  9. data/lib/rubocop/cli.rb +73 -52
  10. data/lib/rubocop/config.rb +8 -5
  11. data/lib/rubocop/cop/access_control.rb +41 -0
  12. data/lib/rubocop/cop/alias.rb +7 -5
  13. data/lib/rubocop/cop/align_parameters.rb +20 -96
  14. data/lib/rubocop/cop/and_or.rb +26 -0
  15. data/lib/rubocop/cop/ascii_comments.rb +3 -8
  16. data/lib/rubocop/cop/ascii_identifiers.rb +6 -5
  17. data/lib/rubocop/cop/avoid_class_vars.rb +5 -10
  18. data/lib/rubocop/cop/avoid_for.rb +7 -5
  19. data/lib/rubocop/cop/avoid_global_vars.rb +19 -7
  20. data/lib/rubocop/cop/avoid_perl_backrefs.rb +7 -10
  21. data/lib/rubocop/cop/avoid_perlisms.rb +11 -10
  22. data/lib/rubocop/cop/block_comments.rb +4 -6
  23. data/lib/rubocop/cop/blocks.rb +11 -47
  24. data/lib/rubocop/cop/case_indentation.rb +9 -31
  25. data/lib/rubocop/cop/class_and_module_camel_case.rb +20 -11
  26. data/lib/rubocop/cop/class_methods.rb +5 -10
  27. data/lib/rubocop/cop/collection_methods.rb +16 -16
  28. data/lib/rubocop/cop/colon_method_call.rb +8 -32
  29. data/lib/rubocop/cop/constant_name.rb +24 -0
  30. data/lib/rubocop/cop/cop.rb +20 -78
  31. data/lib/rubocop/cop/def_parentheses.rb +43 -35
  32. data/lib/rubocop/cop/empty_line_between_defs.rb +11 -15
  33. data/lib/rubocop/cop/empty_lines.rb +20 -9
  34. data/lib/rubocop/cop/empty_literal.rb +47 -0
  35. data/lib/rubocop/cop/encoding.rb +3 -3
  36. data/lib/rubocop/cop/end_of_line.rb +3 -3
  37. data/lib/rubocop/cop/ensure_return.rb +6 -23
  38. data/lib/rubocop/cop/eval.rb +7 -10
  39. data/lib/rubocop/cop/favor_join.rb +9 -24
  40. data/lib/rubocop/cop/favor_modifier.rb +38 -48
  41. data/lib/rubocop/cop/favor_percent_r.rb +7 -7
  42. data/lib/rubocop/cop/favor_sprintf.rb +8 -24
  43. data/lib/rubocop/cop/favor_unless_over_negated_if.rb +19 -17
  44. data/lib/rubocop/cop/handle_exceptions.rb +7 -11
  45. data/lib/rubocop/cop/hash_syntax.rb +29 -14
  46. data/lib/rubocop/cop/if_then_else.rb +32 -29
  47. data/lib/rubocop/cop/leading_comment_space.rb +5 -8
  48. data/lib/rubocop/cop/line_continuation.rb +4 -7
  49. data/lib/rubocop/cop/line_length.rb +3 -3
  50. data/lib/rubocop/cop/loop.rb +33 -0
  51. data/lib/rubocop/cop/method_and_variable_snake_case.rb +42 -19
  52. data/lib/rubocop/cop/method_length.rb +34 -37
  53. data/lib/rubocop/cop/new_lambda_literal.rb +8 -6
  54. data/lib/rubocop/cop/not.rb +10 -4
  55. data/lib/rubocop/cop/numeric_literals.rb +9 -7
  56. data/lib/rubocop/cop/offence.rb +1 -1
  57. data/lib/rubocop/cop/op_method.rb +12 -22
  58. data/lib/rubocop/cop/parameter_lists.rb +12 -6
  59. data/lib/rubocop/cop/parentheses_around_condition.rb +11 -11
  60. data/lib/rubocop/cop/percent_r.rb +7 -7
  61. data/lib/rubocop/cop/reduce_arguments.rb +13 -51
  62. data/lib/rubocop/cop/rescue_exception.rb +13 -29
  63. data/lib/rubocop/cop/rescue_modifier.rb +5 -8
  64. data/lib/rubocop/cop/semicolon.rb +15 -74
  65. data/lib/rubocop/cop/single_line_methods.rb +28 -44
  66. data/lib/rubocop/cop/space_after_comma_etc.rb +29 -9
  67. data/lib/rubocop/cop/space_after_control_keyword.rb +16 -15
  68. data/lib/rubocop/cop/string_literals.rb +9 -35
  69. data/lib/rubocop/cop/surrounding_space.rb +213 -112
  70. data/lib/rubocop/cop/symbol_array.rb +9 -7
  71. data/lib/rubocop/cop/symbol_name.rb +23 -0
  72. data/lib/rubocop/cop/syntax.rb +14 -7
  73. data/lib/rubocop/cop/tab.rb +3 -3
  74. data/lib/rubocop/cop/ternary_operator.rb +26 -24
  75. data/lib/rubocop/cop/trailing_whitespace.rb +3 -5
  76. data/lib/rubocop/cop/trivial_accessors.rb +18 -95
  77. data/lib/rubocop/cop/unless_else.rb +11 -7
  78. data/lib/rubocop/cop/util.rb +26 -0
  79. data/lib/rubocop/cop/variable_interpolation.rb +18 -10
  80. data/lib/rubocop/cop/when_then.rb +6 -17
  81. data/lib/rubocop/cop/word_array.rb +18 -19
  82. data/lib/rubocop/version.rb +1 -1
  83. data/rubocop.gemspec +1 -0
  84. data/spec/project_spec.rb +1 -1
  85. data/spec/rubocop/cli_spec.rb +16 -9
  86. data/spec/rubocop/config_spec.rb +13 -3
  87. data/spec/rubocop/cops/access_control_spec.rb +129 -0
  88. data/spec/rubocop/cops/alias_spec.rb +2 -6
  89. data/spec/rubocop/cops/align_parameters_spec.rb +58 -71
  90. data/spec/rubocop/cops/and_or_spec.rb +37 -0
  91. data/spec/rubocop/cops/ascii_comments_spec.rb +3 -4
  92. data/spec/rubocop/cops/ascii_identifiers_spec.rb +3 -4
  93. data/spec/rubocop/cops/avoid_class_vars_spec.rb +7 -2
  94. data/spec/rubocop/cops/avoid_for_spec.rb +1 -4
  95. data/spec/rubocop/cops/{avoid_global_vars.rb → avoid_global_vars_spec.rb} +4 -4
  96. data/spec/rubocop/cops/avoid_perl_backrefs_spec.rb +1 -1
  97. data/spec/rubocop/cops/avoid_perlisms_spec.rb +5 -5
  98. data/spec/rubocop/cops/block_comments_spec.rb +0 -4
  99. data/spec/rubocop/cops/blocks_spec.rb +33 -0
  100. data/spec/rubocop/cops/case_indentation_spec.rb +5 -5
  101. data/spec/rubocop/cops/class_and_module_camel_case_spec.rb +15 -5
  102. data/spec/rubocop/cops/class_methods_spec.rb +4 -4
  103. data/spec/rubocop/cops/collection_methods_spec.rb +9 -4
  104. data/spec/rubocop/cops/colon_method_call_spec.rb +11 -5
  105. data/spec/rubocop/cops/constant_name_spec.rb +42 -0
  106. data/spec/rubocop/cops/def_with_parentheses_spec.rb +13 -8
  107. data/spec/rubocop/cops/def_without_parentheses_spec.rb +11 -5
  108. data/spec/rubocop/cops/empty_line_between_defs_spec.rb +38 -38
  109. data/spec/rubocop/cops/empty_lines_spec.rb +15 -3
  110. data/spec/rubocop/cops/empty_literal_spec.rb +90 -0
  111. data/spec/rubocop/cops/encoding_spec.rb +9 -9
  112. data/spec/rubocop/cops/end_of_line_spec.rb +2 -2
  113. data/spec/rubocop/cops/ensure_return_spec.rb +1 -3
  114. data/spec/rubocop/cops/eval_spec.rb +8 -5
  115. data/spec/rubocop/cops/favor_join_spec.rb +1 -5
  116. data/spec/rubocop/cops/favor_modifier_spec.rb +16 -14
  117. data/spec/rubocop/cops/{favor_percent_r.rb → favor_percent_r_spec.rb} +6 -6
  118. data/spec/rubocop/cops/favor_sprintf_spec.rb +3 -9
  119. data/spec/rubocop/cops/favor_unless_over_negated_if_spec.rb +4 -4
  120. data/spec/rubocop/cops/favor_until_over_negated_while_spec.rb +3 -3
  121. data/spec/rubocop/cops/handle_exceptions_spec.rb +1 -3
  122. data/spec/rubocop/cops/hash_syntax_spec.rb +11 -6
  123. data/spec/rubocop/cops/if_with_semicolon_spec.rb +7 -1
  124. data/spec/rubocop/cops/leading_comment_space_spec.rb +0 -7
  125. data/spec/rubocop/cops/line_continuation_spec.rb +2 -2
  126. data/spec/rubocop/cops/line_length_spec.rb +2 -2
  127. data/spec/rubocop/cops/loop_spec.rb +31 -0
  128. data/spec/rubocop/cops/method_and_variable_snake_case_spec.rb +38 -12
  129. data/spec/rubocop/cops/method_length_spec.rb +85 -85
  130. data/spec/rubocop/cops/multiline_if_then_spec.rb +15 -15
  131. data/spec/rubocop/cops/new_lambda_literal_spec.rb +3 -3
  132. data/spec/rubocop/cops/not_spec.rb +1 -4
  133. data/spec/rubocop/cops/numeric_literals_spec.rb +13 -13
  134. data/spec/rubocop/cops/one_line_conditional_spec.rb +1 -1
  135. data/spec/rubocop/cops/op_method_spec.rb +2 -9
  136. data/spec/rubocop/cops/parameter_lists_spec.rb +7 -7
  137. data/spec/rubocop/cops/parentheses_around_condition_spec.rb +41 -44
  138. data/spec/rubocop/cops/percent_r_spec.rb +6 -6
  139. data/spec/rubocop/cops/reduce_arguments_spec.rb +4 -4
  140. data/spec/rubocop/cops/rescue_exception_spec.rb +48 -8
  141. data/spec/rubocop/cops/rescue_modifier_spec.rb +2 -5
  142. data/spec/rubocop/cops/semicolon_spec.rb +2 -30
  143. data/spec/rubocop/cops/single_line_methods_spec.rb +13 -13
  144. data/spec/rubocop/cops/space_after_colon_spec.rb +3 -3
  145. data/spec/rubocop/cops/space_after_comma_spec.rb +14 -2
  146. data/spec/rubocop/cops/space_after_control_keyword_spec.rb +42 -3
  147. data/spec/rubocop/cops/space_after_semicolon_spec.rb +2 -2
  148. data/spec/rubocop/cops/space_around_braces_spec.rb +18 -3
  149. data/spec/rubocop/cops/space_around_equals_in_default_parameter_spec.rb +4 -4
  150. data/spec/rubocop/cops/space_around_operators_spec.rb +82 -27
  151. data/spec/rubocop/cops/space_inside_brackets_spec.rb +13 -7
  152. data/spec/rubocop/cops/space_inside_hash_literal_braces_spec.rb +14 -9
  153. data/spec/rubocop/cops/space_inside_parens_spec.rb +7 -3
  154. data/spec/rubocop/cops/string_literals_spec.rb +17 -5
  155. data/spec/rubocop/cops/symbol_array_spec.rb +18 -2
  156. data/spec/rubocop/cops/symbol_name_spec.rb +119 -0
  157. data/spec/rubocop/cops/syntax_spec.rb +25 -18
  158. data/spec/rubocop/cops/tab_spec.rb +2 -2
  159. data/spec/rubocop/cops/ternary_operator_spec.rb +13 -17
  160. data/spec/rubocop/cops/trailing_whitespace_spec.rb +3 -3
  161. data/spec/rubocop/cops/trivial_accessors_spec.rb +17 -20
  162. data/spec/rubocop/cops/unless_else_spec.rb +8 -8
  163. data/spec/rubocop/cops/variable_interpolation_spec.rb +0 -5
  164. data/spec/rubocop/cops/when_then_spec.rb +14 -21
  165. data/spec/rubocop/cops/word_array_spec.rb +12 -4
  166. data/spec/spec_helper.rb +12 -4
  167. metadata +40 -31
  168. data/.document +0 -5
  169. data/lib/rubocop/cop/ampersands_pipes_vs_and_or.rb +0 -25
  170. data/lib/rubocop/cop/array_literal.rb +0 -61
  171. data/lib/rubocop/cop/brace_after_percent.rb +0 -32
  172. data/lib/rubocop/cop/grammar.rb +0 -138
  173. data/lib/rubocop/cop/hash_literal.rb +0 -61
  174. data/lib/rubocop/cop/percent_literals.rb +0 -25
  175. data/lib/rubocop/cop/symbol_snake_case.rb +0 -47
  176. data/spec/rubocop/cops/ampersands_pipes_vs_and_or_spec.rb +0 -57
  177. data/spec/rubocop/cops/array_literal_spec.rb +0 -46
  178. data/spec/rubocop/cops/brace_after_percent_spec.rb +0 -33
  179. data/spec/rubocop/cops/grammar_spec.rb +0 -81
  180. data/spec/rubocop/cops/hash_literal_spec.rb +0 -46
  181. data/spec/rubocop/cops/multiline_blocks_spec.rb +0 -24
  182. data/spec/rubocop/cops/percent_literals_spec.rb +0 -47
  183. data/spec/rubocop/cops/single_line_blocks_spec.rb +0 -22
  184. data/spec/rubocop/cops/symbol_snake_case_spec.rb +0 -93
@@ -3,57 +3,41 @@
3
3
  module Rubocop
4
4
  module Cop
5
5
  class SingleLineMethods < Cop
6
- ERROR_MESSAGE = 'Avoid single-line methods.'
6
+ MSG = 'Avoid single-line method definitions.'
7
7
 
8
- def inspect(file, source, tokens, sexp)
9
- if SingleLineMethods.config['AllowIfMethodIsEmpty']
10
- is_empty = empty_methods(sexp)
11
- end
8
+ def allow_empty?
9
+ SingleLineMethods.config['AllowIfMethodIsEmpty']
10
+ end
12
11
 
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
12
+ def on_def(node)
13
+ check(node)
14
+
15
+ super
16
+ end
17
+
18
+ def on_defs(node)
19
+ check(node)
20
+
21
+ super
38
22
  end
39
23
 
40
24
  private
41
25
 
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
26
+ def check(node)
27
+ start_line = node.loc.keyword.line
28
+ end_line = node.loc.end.line
29
+
30
+ if node.type == :def
31
+ empty_body = node.children[2].type == :nil
32
+ else
33
+ empty_body = node.children[3].type == :nil
34
+ end
35
+
36
+ if start_line == end_line && !(allow_empty? && empty_body)
37
+ add_offence(:convention,
38
+ start_line,
39
+ MSG)
55
40
  end
56
- is_empty
57
41
  end
58
42
  end
59
43
  end
@@ -1,40 +1,60 @@
1
1
  # encoding: utf-8
2
2
 
3
+ # rubocop:disable SymbolName
4
+
3
5
  module Rubocop
4
6
  module Cop
5
7
  module SpaceAfterCommaEtc
6
- ERROR_MESSAGE = 'Space missing after %s.'
8
+ MSG = 'Space missing after %s.'
7
9
 
8
- def inspect(file, source, tokens, sexp)
10
+ def inspect(source, tokens, ast, comments)
9
11
  tokens.each_cons(2) do |t1, t2|
10
- if kind(t1) && !whitespace?(t2)
11
- add_offence(:convention, t1.pos.lineno,
12
- sprintf(ERROR_MESSAGE, kind(t1)))
12
+ if kind(t1) && t1.pos.line == t2.pos.line &&
13
+ t2.pos.column == t1.pos.column + offset(t1)
14
+ add_offence(:convention, t1.pos.line, sprintf(MSG, kind(t1)))
13
15
  end
14
16
  end
15
17
  end
18
+
19
+ # The normal offset, i.e., the distance from the punctuation
20
+ # token where a space should be, is 1.
21
+ def offset(token)
22
+ 1
23
+ end
16
24
  end
17
25
 
18
26
  class SpaceAfterComma < Cop
19
27
  include SpaceAfterCommaEtc
28
+
20
29
  def kind(token)
21
- 'comma' if token.type == :on_comma
30
+ 'comma' if token.type == :tCOMMA
22
31
  end
23
32
  end
24
33
 
25
34
  class SpaceAfterSemicolon < Cop
26
35
  include SpaceAfterCommaEtc
36
+
27
37
  def kind(token)
28
- 'semicolon' if token.type == :on_semicolon
38
+ 'semicolon' if token.type == :tSEMI
29
39
  end
30
40
  end
31
41
 
32
42
  class SpaceAfterColon < Cop
33
43
  include SpaceAfterCommaEtc
44
+
45
+ # The colon following a label will not appear in the token
46
+ # array. Instad we get a tLABEL token, whose length we use to
47
+ # calculate where we expect a space.
48
+ def offset(token)
49
+ case token.type
50
+ when :tLABEL then token.text.length + 1
51
+ when :tCOLON then 1
52
+ end
53
+ end
54
+
34
55
  def kind(token)
35
56
  case token.type
36
- when :on_label then 'colon'
37
- when :on_op then 'colon' if token.text == ':'
57
+ when :tLABEL, :tCOLON then 'colon'
38
58
  end
39
59
  end
40
60
  end
@@ -3,24 +3,25 @@
3
3
  module Rubocop
4
4
  module Cop
5
5
  class SpaceAfterControlKeyword < Cop
6
- ERROR_MESSAGE = 'Use space after control keywords.'
6
+ MSG = 'Use space after control keywords.'
7
+ # elsif and unless are handled by on_if.
8
+ KEYWORDS = %w(if case when while until)
7
9
 
8
- KEYWORDS = %w(if elsif case when while until unless)
10
+ def on_keyword(node)
11
+ return if node.loc.is_a?(Parser::Source::Map::Ternary)
9
12
 
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
13
+ exp = node.loc.expression
14
+ kw = node.loc.keyword
15
+ kw_offset = kw.begin_pos - exp.begin_pos
16
+ if exp.source[kw_offset..-1].start_with?(kw.source + '(')
17
+ add_offence(:convention, kw.line, MSG)
18
+ end
19
+ end
22
20
 
23
- prev = t1
21
+ KEYWORDS.each do |keyword|
22
+ define_method(:"on_#{keyword}") do |node|
23
+ on_keyword(node)
24
+ super(node)
24
25
  end
25
26
  end
26
27
  end
@@ -3,44 +3,18 @@
3
3
  module Rubocop
4
4
  module Cop
5
5
  class StringLiterals < Cop
6
- ERROR_MESSAGE = "Prefer single-quoted strings when you don't need " +
6
+ MSG = "Prefer single-quoted strings when you don't need " +
7
7
  'string interpolation or special symbols.'
8
8
 
9
- def inspect(file, source, tokens, sexp)
10
- state = :outside
11
- tokens.each do |t|
12
- state = case [state, t.type]
13
- when [:outside, :on_tstring_beg]
14
- :double_quote if t.text == '"'
9
+ def inspect(source, tokens, ast, comments)
10
+ on_node(:str, ast, :dstr) do |s|
11
+ text = s.to_a[0]
15
12
 
16
- when [:double_quote, :on_tstring_content]
17
- :valid_double_quote if t.text =~ /'|\\[ntrx]/
18
-
19
- when [:double_quote, :on_embexpr_beg]
20
- :embedded_expression
21
-
22
- when [:double_quote, :on_embvar]
23
- :embedded_variable
24
-
25
- when [:double_quote, :on_tstring_end]
26
- add_offence(:convention, t.pos.lineno, ERROR_MESSAGE)
27
- :outside
28
-
29
- when [:embedded_expression, :on_rbrace]
30
- :valid_double_quote
31
-
32
- when [:embedded_variable, :on_ivar]
33
- :valid_double_quote
34
-
35
- when [:embedded_variable, :on_cvar]
36
- :valid_double_quote
37
-
38
- when [:embedded_variable, :on_gvar]
39
- :valid_double_quote
40
-
41
- when [:valid_double_quote, :on_tstring_end]
42
- :outside
43
- end || state
13
+ if text !~ /['\n\t\r]/ && s.loc.expression.source[0] == '"'
14
+ add_offence(:convention,
15
+ s.loc.line,
16
+ MSG)
17
+ end
44
18
  end
45
19
  end
46
20
  end
@@ -1,179 +1,280 @@
1
1
  # encoding: utf-8
2
2
 
3
+ # rubocop:disable SymbolName
4
+
3
5
  module Rubocop
4
6
  module Cop
5
7
  module SurroundingSpace
6
- def inspect(file, source, tokens, sexp)
7
- @correlations.sort.each do |ix, grammar_path|
8
- check_missing_space(tokens, ix, grammar_path)
8
+ def space_between?(t1, t2)
9
+ char_preceding_2nd_token =
10
+ @source[t2.pos.line - 1][t2.pos.column - 1]
11
+ if char_preceding_2nd_token == '+' && t1.type != :tPLUS
12
+ # Special case. A unary plus is not present in the tokens.
13
+ char_preceding_2nd_token =
14
+ @source[t2.pos.line - 1][t2.pos.column - 2]
9
15
  end
10
- tokens.each_index { |ix| check_unwanted_space(tokens, ix) }
11
- end
12
-
13
- private
14
-
15
- def previous_non_space(tokens, ix)
16
- tokens[0...ix].reverse.find { |t| !whitespace?(t) }
16
+ t2.pos.line > t1.pos.line || char_preceding_2nd_token == ' '
17
17
  end
18
18
 
19
- def ok_without_spaces?(grammar_path)
20
- parent, child = grammar_path.values_at(-2, -1)
21
- return true if [:unary, :symbol, :defs, :def, :call].include?(parent)
22
- return true if [:**, :block_var].include?(child)
23
- parent == :command_call && child == :'::'
19
+ def index_of_first_token(node, tokens)
20
+ @token_table ||= build_token_table(tokens)
21
+ b = node.loc.expression.begin
22
+ @token_table[[b.line, b.column]]
24
23
  end
25
24
 
26
- def surrounded_by_whitespace?(nearby_tokens)
27
- left, _, right = nearby_tokens
28
- whitespace?(left) && whitespace?(right)
25
+ def index_of_last_token(node, tokens)
26
+ @token_table ||= build_token_table(tokens)
27
+ e = node.loc.expression.end
28
+ (0...e.column).to_a.reverse.find do |c|
29
+ ix = @token_table[[e.line, c]]
30
+ return ix if ix
31
+ end
29
32
  end
30
33
 
31
- # Default implementation for classes that don't need it.
32
- def check_missing_space(tokens, ix, grammar_path)
34
+ def build_token_table(tokens)
35
+ table = {}
36
+ tokens.each_with_index do |t, ix|
37
+ table[[t.pos.line, t.pos.column]] = ix
38
+ end
39
+ table
33
40
  end
34
41
  end
35
42
 
36
43
  class SpaceAroundOperators < Cop
37
44
  include SurroundingSpace
38
- ERROR_MESSAGE = 'Surrounding space missing for operator '
39
-
40
- def check_missing_space(tokens, ix, grammar_path)
41
- t = tokens[ix]
42
- if t.type == :on_op
43
- unless surrounded_by_whitespace?(tokens[ix - 1, 3])
44
- unless ok_without_spaces?(grammar_path)
45
- add_offence(:convention, t.pos.lineno,
46
- ERROR_MESSAGE + "'#{t.text}'.")
45
+ MSG_MISSING = "Surrounding space missing for operator '%s'."
46
+ MSG_DETECTED = 'Space around operator ** detected.'
47
+
48
+ BINARY_OPERATORS =
49
+ [:tEQL, :tAMPER2, :tPIPE, :tCARET, :tPLUS, :tMINUS, :tSTAR2,
50
+ :tDIVIDE, :tPERCENT, :tEH, :tCOLON, :tANDOP, :tOROP, :tMATCH,
51
+ :tNMATCH, :tEQ, :tNEQ, :tGT, :tRSHFT, :tGEQ, :tLT,
52
+ :tLSHFT, :tLEQ, :tASSOC, :tEQQ, :tCMP, :tOP_ASGN]
53
+
54
+ def inspect(source, tokens, sexp, comments)
55
+ @source = source
56
+ positions_not_to_check = get_positions_not_to_check(tokens, sexp)
57
+
58
+ tokens.each_cons(3) do |token_before, token, token_after|
59
+ next if token_before.type == :kDEF # TODO: remove?
60
+ next if positions_not_to_check.include?(token.pos)
61
+
62
+ case token.type
63
+ when :tPOW
64
+ if has_space?(token_before, token, token_after)
65
+ add_offence(:convention, token.pos.line, MSG_DETECTED)
47
66
  end
67
+ when *BINARY_OPERATORS
68
+ check_missing_space(token_before, token, token_after)
48
69
  end
49
70
  end
50
71
  end
51
72
 
52
- def check_unwanted_space(tokens, ix)
53
- prev, t, nxt = tokens.values_at(ix - 1, ix, ix + 1)
54
- if t.type == :on_op && t.text == '**' &&
55
- (whitespace?(prev) || whitespace?(nxt))
56
- add_offence(:convention, t.pos.lineno,
57
- "Space around operator #{t.text} detected.")
73
+ # Returns an array of positions marking the tokens that this cop
74
+ # should not check, either because the token is not an operator
75
+ # or because another cop does the check.
76
+ def get_positions_not_to_check(tokens, sexp)
77
+ positions_not_to_check = []
78
+ do_not_check_block_arg_pipes(sexp, positions_not_to_check)
79
+ do_not_check_param_default(tokens, sexp, positions_not_to_check)
80
+ do_not_check_class_lshift_self(tokens, sexp, positions_not_to_check)
81
+ do_not_check_def_things(tokens, sexp, positions_not_to_check)
82
+ do_not_check_singleton_operator_defs(tokens, sexp,
83
+ positions_not_to_check)
84
+ positions_not_to_check
85
+ end
86
+
87
+ def do_not_check_block_arg_pipes(sexp, positions_not_to_check)
88
+ # each { |a| }
89
+ # ^ ^
90
+ on_node(:block, sexp) do |b|
91
+ on_node(:args, b) do |a|
92
+ positions_not_to_check << a.loc.begin << a.loc.end if a.loc.begin
93
+ end
94
+ end
95
+ end
96
+
97
+ def do_not_check_param_default(tokens, sexp, positions_not_to_check)
98
+ # func(a, b=nil)
99
+ # ^
100
+ on_node(:optarg, sexp) do |optarg|
101
+ _arg, equals, _value = tokens[index_of_first_token(optarg, tokens),
102
+ 3]
103
+ positions_not_to_check << equals.pos
104
+ end
105
+ end
106
+
107
+ def do_not_check_class_lshift_self(tokens, sexp, positions_not_to_check)
108
+ # class <<self
109
+ # ^
110
+ on_node(:sclass, sexp) do |sclass|
111
+ ix = index_of_first_token(sclass, tokens)
112
+ if tokens[ix, 2].map(&:type) == [:kCLASS, :tLSHFT]
113
+ positions_not_to_check << tokens[ix + 1].pos
114
+ end
115
+ end
116
+ end
117
+
118
+ def do_not_check_def_things(tokens, sexp, positions_not_to_check)
119
+ # def +(other)
120
+ # ^
121
+ on_node(:def, sexp) do |def_node|
122
+ # def each &block
123
+ # ^
124
+ # def each *args
125
+ # ^
126
+ on_node([:blockarg, :restarg], def_node) do |arg_node|
127
+ positions_not_to_check << tokens[index_of_first_token(arg_node,
128
+ tokens)].pos
129
+ end
130
+ positions_not_to_check <<
131
+ tokens[index_of_first_token(def_node, tokens) + 1].pos
132
+ end
133
+ end
134
+
135
+ def do_not_check_singleton_operator_defs(tokens, sexp,
136
+ positions_not_to_check)
137
+ # def self.===(other)
138
+ # ^
139
+ on_node(:defs, sexp) do |defs_node|
140
+ _receiver, name, _args = *defs_node
141
+ ix = index_of_first_token(defs_node, tokens)
142
+ name_token = tokens[ix..-1].find { |t| t.text == name.to_s }
143
+ positions_not_to_check << name_token.pos
144
+ end
145
+ end
146
+
147
+ def check_missing_space(token_before, token, token_after)
148
+ unless has_space?(token_before, token, token_after)
149
+ text = token.text.to_s + (token.type == :tOP_ASGN ? '=' : '')
150
+ add_offence(:convention, token.pos.line, MSG_MISSING % text)
58
151
  end
59
152
  end
153
+
154
+ def has_space?(token_before, token, token_after)
155
+ space_between?(token_before, token) && space_between?(token,
156
+ token_after)
157
+ end
60
158
  end
61
159
 
62
160
  class SpaceAroundBraces < Cop
63
161
  include SurroundingSpace
162
+ MSG_LEFT = "Surrounding space missing for '{'."
163
+ MSG_RIGHT = "Space missing to the left of '}'."
164
+
165
+ def inspect(source, tokens, sexp, comments)
166
+ @source = source
167
+ positions_not_to_check = get_positions_not_to_check(tokens, sexp)
168
+ tokens.each_cons(2) do |t1, t2|
169
+ next if ([t1.pos, t2.pos] - positions_not_to_check).size < 2
64
170
 
65
- def check_unwanted_space(tokens, ix)
171
+ type1, type2 = t1.type, t2.type
172
+ # :tLBRACE in hash literals, :tLCURLY otherwise.
173
+ next if [:tLCURLY, :tLBRACE].include?(type1) && type2 == :tRCURLY
174
+ check(t1, t2, MSG_LEFT) if type1 == :tLCURLY || type2 == :tLCURLY
175
+ check(t1, t2, MSG_RIGHT) if type2 == :tRCURLY
176
+ end
66
177
  end
67
178
 
68
- def check_missing_space(tokens, ix, grammar_path)
69
- # Checked by SpaceInsideHashLiteralBraces and not here.
70
- return if grammar_path.last == :hash
179
+ def get_positions_not_to_check(tokens, sexp)
180
+ positions_not_to_check = []
71
181
 
72
- t = tokens[ix]
73
- case t.type
74
- when :on_lbrace
75
- unless surrounded_by_whitespace?(tokens[ix - 1, 3])
76
- add_offence(:convention, t.pos.lineno,
77
- "Surrounding space missing for '{'.")
78
- end
79
- when :on_rbrace
80
- unless whitespace?(tokens[ix - 1])
81
- add_offence(:convention, t.pos.lineno,
82
- "Space missing to the left of '}'.")
182
+ on_node(:hash, sexp) do |hash|
183
+ b_ix = index_of_first_token(hash, tokens)
184
+ e_ix = index_of_last_token(hash, tokens)
185
+ positions_not_to_check << tokens[b_ix].pos << tokens[e_ix].pos
186
+ end
187
+
188
+ # TODO: Check braces inside string/symbol/regexp/xstr interpolation.
189
+ on_node([:dstr, :dsym, :regexp, :xstr], sexp) do |s|
190
+ b_ix = index_of_first_token(s, tokens)
191
+ e_ix = index_of_last_token(s, tokens)
192
+ tokens[b_ix..e_ix].each do |t|
193
+ positions_not_to_check << t.pos if t.type == :tRCURLY
83
194
  end
84
195
  end
196
+
197
+ positions_not_to_check
198
+ end
199
+
200
+ def check(t1, t2, msg)
201
+ unless space_between?(t1, t2)
202
+ add_offence(:convention, t1.pos.line, msg)
203
+ end
85
204
  end
86
205
  end
87
206
 
88
207
  module SpaceInside
89
208
  include SurroundingSpace
209
+ MSG = 'Space inside %s detected.'
90
210
 
91
- Paren = Struct.new :left, :right, :kind
92
-
93
- def check_unwanted_space(tokens, ix)
94
- paren = get_paren
95
- prev, t, nxt = tokens.values_at(ix - 1, ix, ix + 1)
96
- offence_detected = case t.type
97
- when paren.left
98
- nxt.type == :on_sp
99
- when paren.right
100
- if prev.type == :on_sp
101
- prev_ns = previous_non_space(tokens, ix)
102
- prev_ns &&
103
- prev_ns.pos.lineno == tokens[ix].pos.lineno &&
104
- # Avoid double reporting
105
- prev_ns.type != paren.left
106
- end
107
- end
108
- if offence_detected
109
- add_offence(:convention, t.pos.lineno,
110
- "Space inside #{paren.kind} detected.")
211
+ def inspect(source, tokens, sexp, comments)
212
+ @source = source
213
+ left, right, kind = specifics
214
+ tokens.each_cons(2) do |t1, t2|
215
+ if t1.type == left || t2.type == right
216
+ if t2.pos.line == t1.pos.line && space_between?(t1, t2)
217
+ add_offence(:convention, t1.pos.line, MSG % kind)
218
+ end
219
+ end
111
220
  end
112
221
  end
113
222
  end
114
223
 
115
224
  class SpaceInsideParens < Cop
116
225
  include SpaceInside
117
- def get_paren
118
- Paren.new(:on_lparen, :on_rparen, 'parentheses')
226
+
227
+ def specifics
228
+ [:tLPAREN2, :tRPAREN, 'parentheses']
119
229
  end
120
230
  end
121
231
 
122
232
  class SpaceInsideBrackets < Cop
123
233
  include SpaceInside
124
- def get_paren
125
- Paren.new(:on_lbracket, :on_rbracket, 'square brackets')
234
+
235
+ def specifics
236
+ [:tLBRACK, :tRBRACK, 'square brackets']
126
237
  end
127
238
  end
128
239
 
129
240
  class SpaceInsideHashLiteralBraces < Cop
130
241
  include SurroundingSpace
242
+ MSG = 'Space inside hash literal braces %s.'
131
243
 
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
244
+ def inspect(source, tokens, sexp, comments)
245
+ @source = source
246
+ on_node(:hash, sexp) do |hash|
247
+ b_ix = index_of_first_token(hash, tokens)
248
+ e_ix = index_of_last_token(hash, tokens)
249
+ check(tokens[b_ix], tokens[b_ix + 1])
250
+ check(tokens[e_ix - 1], tokens[e_ix])
146
251
  end
147
252
  end
148
253
 
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
254
+ def check(t1, t2)
255
+ types = [t1, t2].map(&:type)
256
+ braces = [:tLBRACE, :tRCURLY]
257
+ return if types == braces || (braces - types).size == 2
258
+ has_space = space_between?(t1, t2)
259
+ is_offence, word = if self.class.config['EnforcedStyleIsWithSpaces']
260
+ [!has_space, 'missing']
261
+ else
262
+ [has_space, 'detected']
263
+ end
264
+ add_offence(:convention, t1.pos.line, MSG % word) if is_offence
163
265
  end
164
266
  end
165
267
 
166
268
  class SpaceAroundEqualsInParameterDefault < Cop
167
- def inspect(file, source, tokens, sexp)
168
- each(:params, sexp) do |s|
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.')
176
- end
269
+ include SurroundingSpace
270
+ MSG = 'Surrounding space missing in default value assignment.'
271
+
272
+ def inspect(source, tokens, sexp, comments)
273
+ @source = source
274
+ on_node(:optarg, sexp) do |optarg|
275
+ arg, equals, value = tokens[index_of_first_token(optarg, tokens), 3]
276
+ unless space_between?(arg, equals) && space_between?(equals, value)
277
+ add_offence(:convention, equals.pos.line, MSG)
177
278
  end
178
279
  end
179
280
  end