rubocop 0.40.0 → 0.41.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1014
  3. data/config/default.yml +61 -5
  4. data/config/disabled.yml +6 -0
  5. data/config/enabled.yml +63 -4
  6. data/lib/rubocop.rb +17 -1
  7. data/lib/rubocop/ast_node.rb +56 -42
  8. data/lib/rubocop/ast_node/traversal.rb +3 -3
  9. data/lib/rubocop/cli.rb +14 -9
  10. data/lib/rubocop/comment_config.rb +85 -32
  11. data/lib/rubocop/config.rb +29 -8
  12. data/lib/rubocop/config_loader.rb +1 -1
  13. data/lib/rubocop/cop/cop.rb +1 -1
  14. data/lib/rubocop/cop/corrector.rb +13 -0
  15. data/lib/rubocop/cop/lint/block_alignment.rb +25 -11
  16. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +5 -2
  17. data/lib/rubocop/cop/lint/inherit_exception.rb +69 -0
  18. data/lib/rubocop/cop/lint/percent_string_array.rb +60 -0
  19. data/lib/rubocop/cop/lint/percent_symbol_array.rb +57 -0
  20. data/lib/rubocop/cop/lint/shadowed_exception.rb +95 -0
  21. data/lib/rubocop/cop/lint/useless_access_modifier.rb +28 -13
  22. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +25 -19
  23. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +16 -8
  24. data/lib/rubocop/cop/mixin/if_node.rb +1 -2
  25. data/lib/rubocop/cop/mixin/integer_node.rb +13 -0
  26. data/lib/rubocop/cop/mixin/match_range.rb +26 -0
  27. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +16 -7
  28. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +18 -1
  29. data/lib/rubocop/cop/mixin/negative_conditional.rb +6 -4
  30. data/lib/rubocop/cop/mixin/percent_literal.rb +10 -0
  31. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +24 -6
  32. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +20 -7
  33. data/lib/rubocop/cop/mixin/string_literals_help.rb +2 -2
  34. data/lib/rubocop/cop/mixin/trailing_comma.rb +34 -20
  35. data/lib/rubocop/cop/performance/flat_map.rb +23 -10
  36. data/lib/rubocop/cop/performance/push_splat.rb +47 -0
  37. data/lib/rubocop/cop/performance/redundant_block_call.rb +24 -1
  38. data/lib/rubocop/cop/performance/redundant_merge.rb +3 -5
  39. data/lib/rubocop/cop/performance/sample.rb +15 -11
  40. data/lib/rubocop/cop/rails/exit.rb +62 -0
  41. data/lib/rubocop/cop/rails/output_safety.rb +45 -0
  42. data/lib/rubocop/cop/rails/pluralization_grammar.rb +12 -4
  43. data/lib/rubocop/cop/rails/request_referer.rb +40 -0
  44. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +63 -28
  45. data/lib/rubocop/cop/rails/validation.rb +37 -23
  46. data/lib/rubocop/cop/style/alias.rb +10 -6
  47. data/lib/rubocop/cop/style/bare_percent_literals.rb +18 -7
  48. data/lib/rubocop/cop/style/block_delimiters.rb +15 -22
  49. data/lib/rubocop/cop/style/closing_parenthesis_indentation.rb +19 -8
  50. data/lib/rubocop/cop/style/comment_indentation.rb +13 -5
  51. data/lib/rubocop/cop/style/conditional_assignment.rb +111 -59
  52. data/lib/rubocop/cop/style/documentation.rb +7 -1
  53. data/lib/rubocop/cop/style/each_for_simple_loop.rb +43 -0
  54. data/lib/rubocop/cop/style/each_with_object.rb +25 -14
  55. data/lib/rubocop/cop/style/empty_else.rb +6 -10
  56. data/lib/rubocop/cop/style/extra_spacing.rb +20 -3
  57. data/lib/rubocop/cop/style/file_name.rb +16 -4
  58. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  59. data/lib/rubocop/cop/style/hash_syntax.rb +9 -2
  60. data/lib/rubocop/cop/style/if_unless_modifier.rb +20 -13
  61. data/lib/rubocop/cop/style/implicit_runtime_error.rb +32 -0
  62. data/lib/rubocop/cop/style/infinite_loop.rb +42 -5
  63. data/lib/rubocop/cop/style/lambda.rb +22 -0
  64. data/lib/rubocop/cop/style/method_def_parentheses.rb +12 -4
  65. data/lib/rubocop/cop/style/module_function.rb +28 -6
  66. data/lib/rubocop/cop/style/multiline_method_call_indentation.rb +49 -12
  67. data/lib/rubocop/cop/style/mutable_constant.rb +8 -1
  68. data/lib/rubocop/cop/style/nested_modifier.rb +1 -1
  69. data/lib/rubocop/cop/style/next.rb +43 -31
  70. data/lib/rubocop/cop/style/not.rb +33 -13
  71. data/lib/rubocop/cop/style/numeric_literal_prefix.rb +92 -0
  72. data/lib/rubocop/cop/style/numeric_literals.rb +1 -4
  73. data/lib/rubocop/cop/style/parallel_assignment.rb +26 -8
  74. data/lib/rubocop/cop/style/{deprecated_hash_methods.rb → preferred_hash_methods.rb} +8 -8
  75. data/lib/rubocop/cop/style/redundant_parentheses.rb +29 -19
  76. data/lib/rubocop/cop/style/redundant_self.rb +13 -6
  77. data/lib/rubocop/cop/style/space_after_not.rb +7 -5
  78. data/lib/rubocop/cop/style/space_around_keyword.rb +6 -0
  79. data/lib/rubocop/cop/style/space_around_operators.rb +5 -1
  80. data/lib/rubocop/cop/style/space_before_first_arg.rb +21 -9
  81. data/lib/rubocop/cop/style/space_inside_array_percent_literal.rb +53 -0
  82. data/lib/rubocop/cop/style/space_inside_block_braces.rb +2 -2
  83. data/lib/rubocop/cop/style/space_inside_hash_literal_braces.rb +26 -6
  84. data/lib/rubocop/cop/style/space_inside_percent_literal_delimiters.rb +64 -0
  85. data/lib/rubocop/cop/style/string_literals.rb +37 -8
  86. data/lib/rubocop/cop/style/symbol_array.rb +21 -12
  87. data/lib/rubocop/cop/style/symbol_proc.rb +26 -19
  88. data/lib/rubocop/cop/style/word_array.rb +1 -5
  89. data/lib/rubocop/cop/style/zero_length_predicate.rb +6 -6
  90. data/lib/rubocop/cop/team.rb +40 -27
  91. data/lib/rubocop/cop/util.rb +13 -42
  92. data/lib/rubocop/formatter/disabled_config_formatter.rb +37 -14
  93. data/lib/rubocop/formatter/html_formatter.rb +3 -7
  94. data/lib/rubocop/result_cache.rb +18 -4
  95. data/{spec/support → lib/rubocop/rspec}/cop_helper.rb +3 -0
  96. data/lib/rubocop/rspec/host_environment_simulation_helper.rb +33 -0
  97. data/lib/rubocop/rspec/shared_contexts.rb +75 -0
  98. data/lib/rubocop/rspec/shared_examples.rb +101 -0
  99. data/lib/rubocop/rspec/support.rb +9 -0
  100. data/lib/rubocop/runner.rb +2 -2
  101. data/lib/rubocop/string_interpreter.rb +58 -0
  102. data/lib/rubocop/version.rb +1 -1
  103. metadata +27 -7
@@ -19,33 +19,18 @@ module RuboCop
19
19
 
20
20
  def on_block(node)
21
21
  block_send_or_super, block_args, block_body = *node
22
-
23
- if super?(block_send_or_super)
24
- bmethod_name = :super
25
- else
26
- _breceiver, bmethod_name, _bargs = *block_send_or_super
27
- end
22
+ block_method_name = resolve_block_method_name(block_send_or_super)
28
23
 
29
24
  # TODO: Rails-specific handling that we should probably make
30
25
  # configurable - https://github.com/bbatsov/rubocop/issues/1485
31
26
  # we should ignore lambdas & procs
32
27
  return if block_send_or_super == PROC_NODE
33
- return if [:lambda, :proc].include?(bmethod_name)
34
- return if ignored_method?(bmethod_name)
28
+ return if [:lambda, :proc].include?(block_method_name)
29
+ return if ignored_method?(block_method_name)
35
30
  return unless can_shorten?(block_args, block_body)
36
31
 
37
32
  _receiver, method_name, _args = *block_body
38
-
39
- sb = node.source_range.source_buffer
40
- block_start = node.loc.begin.begin_pos
41
- block_end = node.loc.end.end_pos
42
- range = Parser::Source::Range.new(sb, block_start, block_end)
43
-
44
- add_offense(node,
45
- range,
46
- format(MSG,
47
- method_name,
48
- bmethod_name))
33
+ offense(node, method_name, block_method_name)
49
34
  end
50
35
 
51
36
  def autocorrect(node)
@@ -62,6 +47,28 @@ module RuboCop
62
47
  end
63
48
  end
64
49
 
50
+ private
51
+
52
+ def resolve_block_method_name(block_send_or_super)
53
+ return :super if super?(block_send_or_super)
54
+
55
+ _receiver, method_name, _args = *block_send_or_super
56
+ method_name
57
+ end
58
+
59
+ def offense(node, method_name, block_method_name)
60
+ sb = node.source_range.source_buffer
61
+ block_start = node.loc.begin.begin_pos
62
+ block_end = node.loc.end.end_pos
63
+ range = Parser::Source::Range.new(sb, block_start, block_end)
64
+
65
+ add_offense(node,
66
+ range,
67
+ format(MSG,
68
+ method_name,
69
+ block_method_name))
70
+ end
71
+
65
72
  def autocorrect_method(corrector, node, args, method_name)
66
73
  if args.empty?
67
74
  autocorrect_no_args(corrector, node, method_name)
@@ -27,7 +27,7 @@ module RuboCop
27
27
  def autocorrect(node)
28
28
  words = node.children
29
29
  if style == :percent
30
- escape = words.any? { |w| double_quotes_required?(w.children[0]) }
30
+ escape = words.any? { |w| needs_escaping?(w.children[0]) }
31
31
  char = escape ? 'W' : 'w'
32
32
  contents = autocorrect_words(words, escape, node.loc.line)
33
33
  lambda do |corrector|
@@ -107,10 +107,6 @@ module RuboCop
107
107
  end.join(' ')
108
108
  end
109
109
 
110
- def escape_string(string)
111
- string.inspect[1..-2].tap { |s| s.gsub!(/\\"/, '"') }
112
- end
113
-
114
110
  def style_detected(style, ary_size)
115
111
  cfg = config_to_allow_offenses
116
112
  return if cfg['Enabled'] == false
@@ -41,15 +41,15 @@ module RuboCop
41
41
  end
42
42
 
43
43
  def_node_matcher :zero_length_predicate, <<-END
44
- {(send (send _ ${:length :size}) $:== (int $0))
45
- (send (int $0) $:== (send _ ${:length :size}))
46
- (send (send _ ${:length :size}) $:< (int $1))
47
- (send (int $1) $:> (send _ ${:length :size}))}
44
+ {(send (send (...) ${:length :size}) $:== (int $0))
45
+ (send (int $0) $:== (send (...) ${:length :size}))
46
+ (send (send (...) ${:length :size}) $:< (int $1))
47
+ (send (int $1) $:> (send (...) ${:length :size}))}
48
48
  END
49
49
 
50
50
  def_node_matcher :nonzero_length_predicate, <<-END
51
- {(send (send _ ${:length :size}) ${:> :!=} (int $0))
52
- (send (int $0) ${:< :!=} (send _ ${:length :size}))}
51
+ {(send (send (...) ${:length :size}) ${:> :!=} (int $0))
52
+ (send (int $0) ${:< :!=} (send (...) ${:length :size}))}
53
53
  END
54
54
 
55
55
  def autocorrect(node)
@@ -9,7 +9,9 @@ module RuboCop
9
9
  # bad things are liable to happen
10
10
  INCOMPATIBLE_COPS = {
11
11
  Style::SymbolProc => [Style::SpaceBeforeBlockBraces],
12
- Style::SpaceBeforeBlockBraces => [Style::SymbolProc]
12
+ Style::SpaceBeforeBlockBraces => [Style::SymbolProc],
13
+ Style::LineEndConcatenation => [Style::UnneededInterpolation],
14
+ Style::UnneededInterpolation => [Style::LineEndConcatenation]
13
15
  }.freeze
14
16
 
15
17
  DEFAULT_OPTIONS = {
@@ -17,6 +19,8 @@ module RuboCop
17
19
  debug: false
18
20
  }.freeze
19
21
 
22
+ Investigation = Struct.new(:offenses, :errors)
23
+
20
24
  attr_reader :errors, :warnings, :updated_source_file
21
25
 
22
26
  alias updated_source_file? updated_source_file
@@ -45,32 +49,7 @@ module RuboCop
45
49
  return Lint::Syntax.offenses_from_processed_source(processed_source)
46
50
  end
47
51
 
48
- # The autocorrection process may have to be repeated multiple times
49
- # until there are no corrections left to perform
50
- # To speed things up, run auto-correcting cops by themselves, and only
51
- # run the other cops when no corrections are left
52
- autocorrect_cops, other_cops = cops.partition(&:autocorrect?)
53
- offenses = []
54
- errors = {}
55
-
56
- if autocorrect_cops.any?
57
- commissioner = Commissioner.new(autocorrect_cops,
58
- forces_for(autocorrect_cops))
59
- offenses = commissioner.investigate(processed_source)
60
- if autocorrect(processed_source.buffer, autocorrect_cops)
61
- # We corrected some errors. Another round of inspection will be
62
- # done, and any other offenses will be caught then, so we don't
63
- # need to continue.
64
- return offenses
65
- end
66
- errors = commissioner.errors
67
- end
68
-
69
- commissioner = Commissioner.new(other_cops, forces_for(other_cops))
70
- offenses.concat(commissioner.investigate(processed_source))
71
- errors.merge!(commissioner.errors)
72
- process_commissioner_errors(processed_source.path, errors)
73
- offenses
52
+ offenses(processed_source)
74
53
  end
75
54
 
76
55
  def cops
@@ -93,6 +72,40 @@ module RuboCop
93
72
 
94
73
  private
95
74
 
75
+ def offenses(processed_source)
76
+ # The autocorrection process may have to be repeated multiple times
77
+ # until there are no corrections left to perform
78
+ # To speed things up, run auto-correcting cops by themselves, and only
79
+ # run the other cops when no corrections are left
80
+ autocorrect_cops, other_cops = cops.partition(&:autocorrect?)
81
+
82
+ autocorrect =
83
+ investigate(autocorrect_cops, processed_source) do |offenses|
84
+ # We corrected some errors. Another round of inspection will be
85
+ # done, and any other offenses will be caught then, so we don't
86
+ # need to continue.
87
+ return offenses if autocorrect(processed_source.buffer,
88
+ autocorrect_cops)
89
+ end
90
+
91
+ other = investigate(other_cops, processed_source)
92
+
93
+ errors = autocorrect.errors.merge(other.errors)
94
+ process_commissioner_errors(processed_source.path, errors)
95
+
96
+ autocorrect.offenses.concat(other.offenses)
97
+ end
98
+
99
+ def investigate(cops, processed_source)
100
+ return Investigation.new([], {}) if cops.empty?
101
+
102
+ commissioner = Commissioner.new(cops, forces_for(cops))
103
+ offenses = commissioner.investigate(processed_source)
104
+ yield offenses if block_given?
105
+
106
+ Investigation.new(offenses, commissioner.errors)
107
+ end
108
+
96
109
  def cop_enabled?(cop_class)
97
110
  @config.cop_enabled?(cop_class) ||
98
111
  (@options[:only] || []).include?(cop_class.cop_name)
@@ -23,19 +23,6 @@ module RuboCop
23
23
  + - * / % ** ~ +@ -@ [] []= ! != !~
24
24
  ).map(&:to_sym).push(:'`').freeze
25
25
 
26
- STRING_ESCAPES = {
27
- '\a' => "\a", '\b' => "\b", '\e' => "\e", '\f' => "\f", '\n' => "\n",
28
- '\r' => "\r", '\s' => ' ', '\t' => "\t", '\v' => "\v", "\\\n" => ''
29
- }.freeze
30
- STRING_ESCAPE_REGEX = /\\(?:
31
- [abefnrstv\n] | # simple escapes (above)
32
- \d{1,3} | # octal byte escape
33
- x\d{1,2} | # hex byte escape
34
- u[0-9a-fA-F]{4} | # unicode char escape
35
- u\{[^}]*\} | # extended unicode escape
36
- . # any other escaped char
37
- )/x
38
-
39
26
  # Match literal regex characters, not including anchors, character
40
27
  # classes, alternatives, groups, repetitions, references, etc
41
28
  LITERAL_REGEX = /[\w\s\-,"'!#%&<>=;:`~]|\\[^AbBdDgGkwWszZS0-9]/
@@ -225,7 +212,7 @@ module RuboCop
225
212
 
226
213
  # Regex matches IF there is a ' or there is a \\ in the string that is
227
214
  # not preceded/followed by another \\ (e.g. "\\x34") but not "\\\\".
228
- string.inspect =~ /'|(?<! \\) \\{2}* \\ (?![\\"])/x
215
+ string =~ /'|(?<! \\) \\{2}* \\ (?![\\"])/x
229
216
  end
230
217
 
231
218
  # If double quoted string literals are found in Ruby code, and they are
@@ -233,12 +220,20 @@ module RuboCop
233
220
  def double_quotes_acceptable?(string)
234
221
  # If a string literal contains hard-to-type characters which would
235
222
  # not appear on a "normal" keyboard, then double-quotes are acceptable
236
- double_quotes_required?(string) ||
223
+ needs_escaping?(string) ||
237
224
  string.codepoints.any? { |cp| cp < 32 || cp > 126 }
238
225
  end
239
226
 
227
+ def needs_escaping?(string)
228
+ double_quotes_required?(escape_string(string))
229
+ end
230
+
231
+ def escape_string(string)
232
+ string.inspect[1..-2].tap { |s| s.gsub!(/\\"/, '"') }
233
+ end
234
+
240
235
  def to_string_literal(string)
241
- if double_quotes_required?(string)
236
+ if needs_escaping?(string)
242
237
  string.inspect
243
238
  else
244
239
  "'#{string.gsub('\\') { '\\\\' }}'"
@@ -246,35 +241,11 @@ module RuboCop
246
241
  end
247
242
 
248
243
  def to_symbol_literal(string)
249
- if string =~ /\s/ || double_quotes_required?(string)
250
- ":#{to_string_literal(string)}"
251
- else
252
- ":#{string}"
253
- end
244
+ ":#{string.to_sym}"
254
245
  end
255
246
 
256
- # Take a string with embedded escapes, and convert the escapes as the Ruby
257
- # interpreter would when reading a double-quoted string literal.
258
- # For example, "\\n" will be converted to "\n".
259
247
  def interpret_string_escapes(string)
260
- # We currently don't handle \cx, \C-x, and \M-x
261
- string.gsub(STRING_ESCAPE_REGEX) do |escape|
262
- STRING_ESCAPES[escape] || begin
263
- if escape[1] == 'x'
264
- [escape[2..-1].hex].pack('C')
265
- elsif escape[1] == 'u'
266
- if escape[2] == '{'
267
- escape[3..-1].split(/\s+/).map(&:hex).pack('U')
268
- else
269
- [escape[2..-1].hex].pack('U')
270
- end
271
- elsif escape[1] =~ /\d/ # octal escape
272
- [escape[1..-1].to_i(8)].pack('C')
273
- else
274
- escape[1] # literal escaped char, like \\
275
- end
276
- end
277
- end
248
+ StringInterpreter.interpret(string)
278
249
  end
279
250
  end
280
251
  end
@@ -25,6 +25,12 @@ module RuboCop
25
25
  attr_accessor :config_to_allow_offenses, :detected_styles
26
26
  end
27
27
 
28
+ def initialize(output, options = {})
29
+ super
30
+ @cops_with_offenses ||= Hash.new(0)
31
+ @files_with_offenses ||= {}
32
+ end
33
+
28
34
  def file_started(_file, _file_info)
29
35
  @exclude_limit_option = @options[:exclude_limit]
30
36
  @exclude_limit = (
@@ -34,8 +40,6 @@ module RuboCop
34
40
  end
35
41
 
36
42
  def file_finished(file, offenses)
37
- @cops_with_offenses ||= Hash.new(0)
38
- @files_with_offenses ||= {}
39
43
  offenses.each do |o|
40
44
  @cops_with_offenses[o.cop_name] += 1
41
45
  @files_with_offenses[o.cop_name] ||= []
@@ -44,27 +48,41 @@ module RuboCop
44
48
  end
45
49
 
46
50
  def finished(_inspected_files)
47
- command = 'rubocop --auto-gen-config'
48
- if @exclude_limit_option
49
- command += format(' --exclude-limit %d', @exclude_limit_option.to_i)
50
- end
51
51
  output.puts HEADING % command
52
52
 
53
53
  # Syntax isn't a real cop and it can't be disabled.
54
54
  @cops_with_offenses.delete('Syntax')
55
55
 
56
- @cops_with_offenses.sort.each do |cop_name, offense_count|
57
- output.puts
58
- cfg = self.class.config_to_allow_offenses[cop_name] || {}
56
+ output_offenses
59
57
 
60
- output_cop_comments(output, cfg, cop_name, offense_count)
61
- output_cop_config(output, cfg, cop_name)
62
- end
63
58
  puts "Created #{output.path}."
64
59
  puts "Run `rubocop --config #{output.path}`, or add `inherit_from: " \
65
60
  "#{output.path}` in a .rubocop.yml file."
66
61
  end
67
62
 
63
+ def command
64
+ command = 'rubocop --auto-gen-config'
65
+ if @exclude_limit_option
66
+ command += format(' --exclude-limit %d', @exclude_limit_option.to_i)
67
+ end
68
+
69
+ command
70
+ end
71
+
72
+ def output_offenses
73
+ @cops_with_offenses.sort.each do |cop_name, offense_count|
74
+ output_cop(cop_name, offense_count)
75
+ end
76
+ end
77
+
78
+ def output_cop(cop_name, offense_count)
79
+ output.puts
80
+ cfg = self.class.config_to_allow_offenses[cop_name] || {}
81
+
82
+ output_cop_comments(output, cfg, cop_name, offense_count)
83
+ output_cop_config(output, cfg, cop_name)
84
+ end
85
+
68
86
  def output_cop_comments(output, cfg, cop_name, offense_count)
69
87
  output.puts "# Offense count: #{offense_count}" if @show_offense_counts
70
88
  if COPS[cop_name] && COPS[cop_name].first.new.support_autocorrect?
@@ -91,12 +109,17 @@ module RuboCop
91
109
  end
92
110
 
93
111
  def output_cop_config(output, cfg, cop_name)
112
+ # 'Enabled' option will be put into file only if exclude
113
+ # limit is exceeded.
114
+ cfg_without_enabled = cfg.reject { |key| key == 'Enabled' }
115
+
94
116
  output.puts "#{cop_name}:"
95
- cfg.each do |key, value|
117
+ cfg_without_enabled.each do |key, value|
96
118
  value = value[0] if value.is_a?(Array)
97
119
  output.puts " #{key}: #{value}"
98
120
  end
99
- output_offending_files(output, cfg, cop_name)
121
+
122
+ output_offending_files(output, cfg_without_enabled, cop_name)
100
123
  end
101
124
 
102
125
  def output_offending_files(output, cfg, cop_name)
@@ -95,18 +95,14 @@ module RuboCop
95
95
 
96
96
  def highlighted_source_line(offense)
97
97
  location = offense.location
98
-
99
- source_line = if location.first_line == location.last_line
100
- location.source_line
101
- else
102
- "#{location.source_line} #{ELLIPSES}"
103
- end
98
+ source_line = location.source_line
104
99
 
105
100
  escape(source_line[0...offense.highlighted_area.begin_pos]) +
106
101
  "<span class=\"highlight #{offense.severity}\">" +
107
102
  escape(offense.highlighted_area.source) +
108
103
  '</span>' +
109
- escape(source_line[offense.highlighted_area.end_pos..-1])
104
+ escape(source_line[offense.highlighted_area.end_pos..-1]) +
105
+ (location.first_line == location.last_line ? '' : " #{ELLIPSES}")
110
106
  end
111
107
 
112
108
  def escape(s)
@@ -41,7 +41,11 @@ module RuboCop
41
41
  private
42
42
 
43
43
  def remove_files(files, dirs, remove_count, verbose)
44
- File.delete(*files[0, remove_count])
44
+ # Batch file deletions, deleting over 130,000+ files will crash
45
+ # File.delete.
46
+ files[0, remove_count].each_slice(10_000).each do |files_slice|
47
+ File.delete(*files_slice)
48
+ end
45
49
  dirs.each { |dir| Dir.rmdir(dir) if Dir["#{dir}/*"].empty? }
46
50
  rescue Errno::ENOENT
47
51
  # This can happen if parallel RuboCop invocations try to remove the
@@ -60,8 +64,14 @@ module RuboCop
60
64
  File.join(root, 'rubocop_cache')
61
65
  end
62
66
 
67
+ def self.allow_symlinks_in_cache_location?(config_store)
68
+ config_store.for('.').for_all_cops['AllowSymlinksInCacheRootDirectory']
69
+ end
70
+
63
71
  def initialize(file, options, config_store, cache_root = nil)
64
72
  cache_root ||= ResultCache.cache_root(config_store)
73
+ @allow_symlinks_in_cache_location =
74
+ ResultCache.allow_symlinks_in_cache_location?(config_store)
65
75
  @path = File.join(cache_root, rubocop_checksum,
66
76
  relevant_options_digest(options),
67
77
  file_checksum(file, config_store))
@@ -81,9 +91,9 @@ module RuboCop
81
91
  FileUtils.mkdir_p(dir)
82
92
  preliminary_path = "#{@path}_#{rand(1_000_000_000)}"
83
93
  # RuboCop must be in control of where its cached data is stored. A
84
- # symbolic link anywhere in the cache directory tree is an indication
85
- # that a symlink attack is being waged.
86
- return if any_symlink?(dir)
94
+ # symbolic link anywhere in the cache directory tree can be an
95
+ # indication that a symlink attack is being waged.
96
+ return if symlink_protection_triggered?(dir)
87
97
 
88
98
  File.open(preliminary_path, 'wb') do |f|
89
99
  f.write(@cached_data.to_json(offenses))
@@ -98,6 +108,10 @@ module RuboCop
98
108
 
99
109
  private
100
110
 
111
+ def symlink_protection_triggered?(path)
112
+ !@allow_symlinks_in_cache_location && any_symlink?(path)
113
+ end
114
+
101
115
  def any_symlink?(path)
102
116
  while path != File.dirname(path)
103
117
  if File.symlink?(path)