rubocop 0.33.0 → 0.34.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -1
  3. data/README.md +65 -6
  4. data/config/default.yml +30 -0
  5. data/config/disabled.yml +5 -0
  6. data/config/enabled.yml +19 -3
  7. data/lib/rubocop.rb +7 -2
  8. data/lib/rubocop/cli.rb +1 -1
  9. data/lib/rubocop/config.rb +11 -6
  10. data/lib/rubocop/config_loader.rb +7 -3
  11. data/lib/rubocop/cop/commissioner.rb +6 -11
  12. data/lib/rubocop/cop/cop.rb +7 -3
  13. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +10 -8
  14. data/lib/rubocop/cop/lint/duplicated_key.rb +37 -0
  15. data/lib/rubocop/cop/lint/end_alignment.rb +6 -6
  16. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +57 -14
  17. data/lib/rubocop/cop/metrics/method_length.rb +1 -3
  18. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -2
  19. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +11 -1
  20. data/lib/rubocop/cop/mixin/configurable_naming.rb +14 -3
  21. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -3
  22. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +9 -0
  23. data/lib/rubocop/cop/mixin/method_preference.rb +28 -0
  24. data/lib/rubocop/cop/mixin/safe_assignment.rb +2 -2
  25. data/lib/rubocop/cop/performance/case_when_splat.rb +132 -0
  26. data/lib/rubocop/cop/performance/string_replacement.rb +45 -28
  27. data/lib/rubocop/cop/rails/action_filter.rb +31 -5
  28. data/lib/rubocop/cop/rails/date.rb +6 -5
  29. data/lib/rubocop/cop/rails/read_write_attribute.rb +1 -1
  30. data/lib/rubocop/cop/rails/time_zone.rb +12 -1
  31. data/lib/rubocop/cop/style/alias.rb +1 -0
  32. data/lib/rubocop/cop/style/block_delimiters.rb +6 -5
  33. data/lib/rubocop/cop/style/collection_methods.rb +2 -20
  34. data/lib/rubocop/cop/style/else_alignment.rb +2 -1
  35. data/lib/rubocop/cop/style/empty_line_between_defs.rb +42 -13
  36. data/lib/rubocop/cop/style/extra_spacing.rb +17 -3
  37. data/lib/rubocop/cop/style/first_parameter_indentation.rb +1 -1
  38. data/lib/rubocop/cop/style/hash_syntax.rb +5 -0
  39. data/lib/rubocop/cop/style/indentation_width.rb +7 -1
  40. data/lib/rubocop/cop/style/initial_indentation.rb +5 -0
  41. data/lib/rubocop/cop/style/multiline_operation_indentation.rb +1 -1
  42. data/lib/rubocop/cop/style/mutable_constant.rb +35 -0
  43. data/lib/rubocop/cop/style/next.rb +31 -14
  44. data/lib/rubocop/cop/style/option_hash.rb +9 -1
  45. data/lib/rubocop/cop/style/parallel_assignment.rb +21 -44
  46. data/lib/rubocop/cop/style/redundant_freeze.rb +37 -0
  47. data/lib/rubocop/cop/style/rescue_ensure_alignment.rb +2 -1
  48. data/lib/rubocop/cop/style/rescue_modifier.rb +35 -3
  49. data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +1 -0
  50. data/lib/rubocop/cop/style/string_methods.rb +32 -0
  51. data/lib/rubocop/cop/style/symbol_proc.rb +54 -12
  52. data/lib/rubocop/cop/style/trailing_blank_lines.rb +1 -1
  53. data/lib/rubocop/cop/team.rb +2 -2
  54. data/lib/rubocop/cop/util.rb +2 -2
  55. data/lib/rubocop/cop/variable_force.rb +10 -10
  56. data/lib/rubocop/cop/variable_force/locatable.rb +5 -5
  57. data/lib/rubocop/formatter/formatter_set.rb +1 -0
  58. data/lib/rubocop/options.rb +24 -1
  59. data/lib/rubocop/result_cache.rb +121 -0
  60. data/lib/rubocop/runner.rb +55 -15
  61. data/lib/rubocop/target_finder.rb +1 -1
  62. data/lib/rubocop/version.rb +1 -1
  63. data/relnotes/v0.34.0.md +182 -0
  64. data/rubocop.gemspec +1 -1
  65. metadata +12 -4
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop check for uses of Object#freeze on immutable objects.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # CONST = 1.freeze
11
+ #
12
+ # # good
13
+ # CONST = 1
14
+ class RedundantFreeze < Cop
15
+ MSG = 'Freezing immutable objects is pointless.'.freeze
16
+
17
+ TARGET_NODES = [:int, :float, :sym].freeze
18
+
19
+ def on_send(node)
20
+ receiver, method_name, *args = *node
21
+
22
+ return unless receiver && TARGET_NODES.include?(receiver.type)
23
+ return unless method_name == :freeze && args.empty?
24
+
25
+ add_offense(node, :expression)
26
+ end
27
+
28
+ def autocorrect(node)
29
+ lambda do |corrector|
30
+ corrector.remove(node.loc.dot)
31
+ corrector.remove(node.loc.selector)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -65,7 +65,8 @@ module RuboCop
65
65
  end
66
66
 
67
67
  def modifier?(node)
68
- @modifier_locations.include? node.loc.keyword
68
+ return false unless @modifier_locations.respond_to?(:include?)
69
+ @modifier_locations.include?(node.loc.keyword)
69
70
  end
70
71
 
71
72
  def ancestor_node(node)
@@ -5,14 +5,46 @@ module RuboCop
5
5
  module Style
6
6
  # This cop checks for uses of rescue in its modifier form.
7
7
  class RescueModifier < Cop
8
+ include AutocorrectAlignment
9
+
8
10
  MSG = 'Avoid using `rescue` in its modifier form.'
9
11
 
10
12
  def investigate(processed_source)
11
- processed_source.tokens.each do |t|
12
- next unless t.type == :kRESCUE_MOD
13
- add_offense(nil, t.pos)
13
+ @modifier_locations = processed_source.tokens
14
+ .select { |t| t.type == :kRESCUE_MOD }
15
+ .map(&:pos)
16
+ end
17
+
18
+ def on_resbody(node)
19
+ return unless modifier?(node)
20
+ add_offense(node.parent, :expression)
21
+ end
22
+
23
+ def autocorrect(node)
24
+ operation, rescue_modifier, = *node
25
+ *_, rescue_args = *rescue_modifier
26
+
27
+ correction =
28
+ "begin\n" <<
29
+ indentation(node) << operation.loc.expression.source <<
30
+ "\n#{offset(node)}rescue\n" <<
31
+ indentation(node) << rescue_args.loc.expression.source <<
32
+ "\n#{offset(node)}end"
33
+ range = Parser::Source::Range.new(node.loc.expression.source_buffer,
34
+ node.loc.expression.begin_pos,
35
+ node.loc.expression.end_pos)
36
+
37
+ lambda do |corrector|
38
+ corrector.replace(range, correction)
14
39
  end
15
40
  end
41
+
42
+ private
43
+
44
+ def modifier?(node)
45
+ return false unless @modifier_locations.respond_to?(:include?)
46
+ @modifier_locations.include?(node.loc.keyword)
47
+ end
16
48
  end
17
49
  end
18
50
  end
@@ -20,6 +20,7 @@ module RuboCop
20
20
  def on_dstr(node)
21
21
  node.children.select { |n| n.type == :begin }.each do |begin_node|
22
22
  final_node = begin_node.children.last
23
+ next unless final_node
23
24
 
24
25
  interp = final_node.loc.expression
25
26
  interp_with_surrounding_space = range_with_surrounding_space(interp)
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop enforces the use of consistent method names
7
+ # from the String class.
8
+ class StringMethods < Cop
9
+ include MethodPreference
10
+
11
+ MSG = 'Prefer `%s` over `%s`.'
12
+
13
+ def on_send(node)
14
+ _receiver, method_name, *_args = *node
15
+ return unless preferred_methods[method_name]
16
+ add_offense(node, :selector,
17
+ format(MSG,
18
+ preferred_method(method_name),
19
+ method_name)
20
+ )
21
+ end
22
+
23
+ def autocorrect(node)
24
+ lambda do |corrector|
25
+ corrector.replace(node.loc.selector,
26
+ preferred_method(node.loc.selector.source))
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -17,18 +17,20 @@ module RuboCop
17
17
  PROC_NODE = s(:send, s(:const, nil, :Proc), :new)
18
18
 
19
19
  def on_block(node)
20
- block_send, block_args, block_body = *node
20
+ block_send_or_super, block_args, block_body = *node
21
21
 
22
- _breceiver, bmethod_name, bargs = *block_send
22
+ if super?(block_send_or_super)
23
+ bmethod_name = :super
24
+ else
25
+ _breceiver, bmethod_name, _bargs = *block_send_or_super
26
+ end
23
27
 
24
28
  # TODO: Rails-specific handling that we should probably make
25
29
  # configurable - https://github.com/bbatsov/rubocop/issues/1485
26
30
  # we should ignore lambdas & procs
27
- return if block_send == PROC_NODE
31
+ return if block_send_or_super == PROC_NODE
28
32
  return if [:lambda, :proc].include?(bmethod_name)
29
33
  return if ignored_method?(bmethod_name)
30
- # File.open(file) { |f| f.readlines }
31
- return if bargs
32
34
  return unless can_shorten?(block_args, block_body)
33
35
 
34
36
  _receiver, method_name, _args = *block_body
@@ -41,16 +43,52 @@ module RuboCop
41
43
 
42
44
  def autocorrect(node)
43
45
  lambda do |corrector|
44
- _block_method, _block_args, block_body = *node
46
+ block_send_or_super, _block_args, block_body = *node
45
47
  _receiver, method_name, _args = *block_body
46
48
 
47
- block_range =
48
- Parser::Source::Range.new(node.loc.expression.source_buffer,
49
- node.loc.begin.begin_pos,
50
- node.loc.end.end_pos)
49
+ if super?(block_send_or_super)
50
+ args = *block_send_or_super
51
+ autocorrect_method(corrector, node, args, method_name)
52
+ else
53
+ _breceiver, _bmethod_name, *args = *block_send_or_super
54
+ autocorrect_method(corrector, node, args, method_name)
55
+ end
56
+ end
57
+ end
51
58
 
52
- corrector.replace(range_with_surrounding_space(block_range, :left),
53
- "(&:#{method_name})")
59
+ def autocorrect_method(corrector, node, args, method_name)
60
+ if args.empty?
61
+ autocorrect_no_args(corrector, node, method_name)
62
+ else
63
+ autocorrect_with_args(corrector, node, args, method_name)
64
+ end
65
+ end
66
+
67
+ def autocorrect_no_args(corrector, node, method_name)
68
+ corrector.replace(block_range_with_space(node), "(&:#{method_name})")
69
+ end
70
+
71
+ def autocorrect_with_args(corrector, node, args, method_name)
72
+ corrector.insert_after(args.last.loc.expression, ", &:#{method_name}")
73
+ corrector.remove(block_range_with_space(node))
74
+ end
75
+
76
+ def block_range_with_space(node)
77
+ block_range =
78
+ Parser::Source::Range.new(node.loc.expression.source_buffer,
79
+ begin_pos_for_replacement(node),
80
+ node.loc.end.end_pos)
81
+ range_with_surrounding_space(block_range, :left)
82
+ end
83
+
84
+ def begin_pos_for_replacement(node)
85
+ block_send_or_super, _block_args, _block_body = *node
86
+ expr = block_send_or_super.loc.expression
87
+
88
+ if (paren_pos = (expr.source =~ /\(\s*\)$/))
89
+ expr.begin_pos + paren_pos
90
+ else
91
+ node.loc.begin.begin_pos
54
92
  end
55
93
  end
56
94
 
@@ -78,6 +116,10 @@ module RuboCop
78
116
 
79
117
  block_arg_name == receiver_name
80
118
  end
119
+
120
+ def super?(node)
121
+ [:super, :zsuper].include?(node.type)
122
+ end
81
123
  end
82
124
  end
83
125
  end
@@ -35,7 +35,7 @@ module RuboCop
35
35
  begin_pos = sb.source.length - whitespace_at_end.length
36
36
  autocorrect_range = Parser::Source::Range.new(sb, begin_pos,
37
37
  sb.source.length)
38
- begin_pos += "\n".length unless whitespace_at_end.length == 0
38
+ begin_pos += 1 unless whitespace_at_end.length == 0
39
39
  report_range = Parser::Source::Range.new(sb, begin_pos,
40
40
  sb.source.length)
41
41
  add_offense(autocorrect_range, report_range,
@@ -97,8 +97,8 @@ module RuboCop
97
97
  file_errors.each do |cop, errors|
98
98
  errors.each do |e|
99
99
  handle_error(e,
100
- "An error occurred while #{cop.name}".color(:red) +
101
- " cop was inspecting #{file}.".color(:red))
100
+ Rainbow("An error occurred while #{cop.name}" /
101
+ " cop was inspecting #{file}.".red))
102
102
  end
103
103
  end
104
104
  end
@@ -186,8 +186,8 @@ module RuboCop
186
186
  end
187
187
 
188
188
  def begins_its_line?(range)
189
- source_before_end = range.source_buffer.source[0...range.begin_pos]
190
- source_before_end =~ /\n\s*\Z/
189
+ source_before_range = range.source_buffer.source[0...range.begin_pos]
190
+ source_before_range.rpartition("\n").last.strip.empty?
191
191
  end
192
192
 
193
193
  def within_node?(inner, outer)
@@ -106,28 +106,28 @@ module RuboCop
106
106
  # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
107
107
  def dispatch_node(node)
108
108
  case node.type
109
- when *ARGUMENT_DECLARATION_TYPES
110
- process_variable_declaration(node)
111
109
  when VARIABLE_ASSIGNMENT_TYPE
112
110
  process_variable_assignment(node)
113
111
  when REGEXP_NAMED_CAPTURE_TYPE
114
112
  process_regexp_named_captures(node)
115
- when *OPERATOR_ASSIGNMENT_TYPES
116
- process_variable_operator_assignment(node)
117
113
  when MULTIPLE_ASSIGNMENT_TYPE
118
114
  process_variable_multiple_assignment(node)
119
115
  when VARIABLE_REFERENCE_TYPE
120
116
  process_variable_referencing(node)
121
- when *LOOP_TYPES
122
- process_loop(node)
123
117
  when RESCUE_TYPE
124
118
  process_rescue(node)
125
119
  when ZERO_ARITY_SUPER_TYPE
126
120
  process_zero_arity_super(node)
127
- when *SCOPE_TYPES
128
- process_scope(node)
129
121
  when SEND_TYPE
130
122
  process_send(node)
123
+ when *ARGUMENT_DECLARATION_TYPES
124
+ process_variable_declaration(node)
125
+ when *OPERATOR_ASSIGNMENT_TYPES
126
+ process_variable_operator_assignment(node)
127
+ when *LOOP_TYPES
128
+ process_loop(node)
129
+ when *SCOPE_TYPES
130
+ process_scope(node)
131
131
  end
132
132
  end
133
133
  # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
@@ -326,13 +326,13 @@ module RuboCop
326
326
  case node.type
327
327
  when :lvar
328
328
  referenced_variable_names_in_loop << node.children.first
329
+ when :lvasgn
330
+ assignment_nodes_in_loop << node
329
331
  when *OPERATOR_ASSIGNMENT_TYPES
330
332
  asgn_node = node.children.first
331
333
  if asgn_node.type == :lvasgn
332
334
  referenced_variable_names_in_loop << asgn_node.children.first
333
335
  end
334
- when :lvasgn
335
- assignment_nodes_in_loop << node
336
336
  end
337
337
  end
338
338
 
@@ -80,9 +80,9 @@ module RuboCop
80
80
  case branch_point_node.type
81
81
  when :if then if_body_name
82
82
  when :case then case_body_name
83
- when *LOGICAL_OPERATOR_TYPES then logical_operator_body_name
84
83
  when RESCUE_TYPE then rescue_body_name
85
84
  when ENSURE_TYPE then ensure_body_name
85
+ when *LOGICAL_OPERATOR_TYPES then logical_operator_body_name
86
86
  else fail InvalidBranchBodyError
87
87
  end
88
88
  rescue InvalidBranchBodyError
@@ -157,14 +157,14 @@ module RuboCop
157
157
  child_index = parent_node.children.index(child_node)
158
158
 
159
159
  case parent_node.type
160
- when *BRANCH_TYPES
161
- child_index != CONDITION_INDEX_OF_BRANCH_NODE
162
- when *LOGICAL_OPERATOR_TYPES
163
- child_index != LEFT_SIDE_INDEX_OF_LOGICAL_OPERATOR_NODE
164
160
  when RESCUE_TYPE
165
161
  true
166
162
  when ENSURE_TYPE
167
163
  child_index != ENSURE_INDEX_OF_ENSURE_NODE
164
+ when *BRANCH_TYPES
165
+ child_index != CONDITION_INDEX_OF_BRANCH_NODE
166
+ when *LOGICAL_OPERATOR_TYPES
167
+ child_index != LEFT_SIDE_INDEX_OF_LOGICAL_OPERATOR_NODE
168
168
  else
169
169
  false
170
170
  end
@@ -52,6 +52,7 @@ module RuboCop
52
52
 
53
53
  offenses = offenses.sort.reject(&:disabled?)
54
54
  each { |f| f.file_finished(file, offenses) }
55
+ offenses
55
56
  end
56
57
 
57
58
  def add_formatter(formatter_type, output_path = nil)
@@ -15,9 +15,16 @@ module RuboCop
15
15
 
16
16
  def parse(args)
17
17
  define_options(args).parse!(args)
18
+ # The --no-color CLI option sets `color: false` so we don't want the
19
+ # `no_color` key, which is created automatically.
20
+ @options.delete(:no_color)
18
21
 
19
22
  validate_compatibility
20
23
 
24
+ if @options[:stdin] && !args.one?
25
+ fail ArgumentError, '-s/--stdin requires exactly one path.'
26
+ end
27
+
21
28
  [@options, args]
22
29
  end
23
30
 
@@ -40,6 +47,7 @@ module RuboCop
40
47
  OptionParser.new do |opts|
41
48
  opts.banner = 'Usage: rubocop [options] [file1, file2, ...]'
42
49
 
50
+ add_list_options(opts)
43
51
  add_only_options(opts)
44
52
  add_configuration_options(opts, args)
45
53
  add_formatting_options(opts)
@@ -57,6 +65,9 @@ module RuboCop
57
65
  (@options[:only] & %w(Lint/UnneededDisable UnneededDisable)).any?
58
66
  fail ArgumentError, 'Lint/UnneededDisable can not be used with --only.'
59
67
  end
68
+ if @options.key?(:cache) && !%w(true false).include?(@options[:cache])
69
+ fail ArgumentError, '-C/--cache argument must be true or false'
70
+ end
60
71
  return unless (incompat = @options.keys & EXITING_OPTIONS).size > 1
61
72
  fail ArgumentError, "Incompatible cli options: #{incompat.inspect}"
62
73
  end
@@ -121,6 +132,7 @@ module RuboCop
121
132
 
122
133
  def add_boolean_flags(opts)
123
134
  option(opts, '-F', '--fail-fast')
135
+ option(opts, '-C', '--cache FLAG')
124
136
  option(opts, '-d', '--debug')
125
137
  option(opts, '-D', '--display-cop-names')
126
138
  option(opts, '-S', '--display-style-guide')
@@ -133,6 +145,11 @@ module RuboCop
133
145
 
134
146
  option(opts, '-v', '--version')
135
147
  option(opts, '-V', '--verbose-version')
148
+ option(opts, '-s', '--stdin') { @options[:stdin] = $stdin.read }
149
+ end
150
+
151
+ def add_list_options(opts)
152
+ option(opts, '-L', '--list-target-files')
136
153
  end
137
154
 
138
155
  # Sets a value in the @options hash, based on the given long option and its
@@ -222,15 +239,21 @@ module RuboCop
222
239
  fail_fast: ['Inspect files in order of modification',
223
240
  'time and stop after the first file',
224
241
  'containing offenses.'],
242
+ cache: ["Use result caching (FLAG=true) or don't",
243
+ '(FLAG=false), default determined by',
244
+ 'configuration parameter AllCops: UseCache.'],
225
245
  debug: 'Display debug info.',
226
246
  display_cop_names: 'Display cop names in offense messages.',
227
247
  display_style_guide: 'Display style guide URLs in offense messages.',
228
248
  rails: 'Run extra Rails cops.',
229
249
  lint: 'Run only lint cops.',
250
+ list_target_files: 'List all files RuboCop will inspect.',
230
251
  auto_correct: 'Auto-correct offenses.',
231
252
  no_color: 'Disable color output.',
232
253
  version: 'Display version.',
233
- verbose_version: 'Display verbose version.'
254
+ verbose_version: 'Display verbose version.',
255
+ stdin: ['Pipe source from STDIN.',
256
+ 'This is useful for editor integration.']
234
257
  }
235
258
  end
236
259
  end
@@ -0,0 +1,121 @@
1
+ # encoding: utf-8
2
+
3
+ require 'digest/md5'
4
+ require 'find'
5
+ require 'tmpdir'
6
+
7
+ module RuboCop
8
+ # Provides functionality for caching rubocop runs.
9
+ class ResultCache
10
+ # Include the user name in the path as a simple means of avoiding write
11
+ # collisions.
12
+ def initialize(file, options, config_store, cache_root = nil)
13
+ cache_root ||= ResultCache.cache_root(config_store)
14
+ @path = File.join(cache_root, rubocop_checksum, relevant_options(options),
15
+ file_checksum(file, config_store))
16
+ end
17
+
18
+ def valid?
19
+ File.exist?(@path)
20
+ end
21
+
22
+ def load
23
+ Marshal.load(IO.read(@path))
24
+ end
25
+
26
+ def save(offenses, disabled_line_ranges, comments)
27
+ FileUtils.mkdir_p(File.dirname(@path))
28
+ preliminary_path = "#{@path}_#{rand(1_000_000_000)}"
29
+ File.open(preliminary_path, 'w') do |f|
30
+ # The Hash[x.sort] call is a trick that converts a Hash with a default
31
+ # block to a Hash without a default block. Thus making it possible to
32
+ # dump.
33
+ f.write(Marshal.dump([offenses, Hash[disabled_line_ranges.sort],
34
+ comments]))
35
+ end
36
+ # The preliminary path is used so that if there are multiple RuboCop
37
+ # processes trying to save data for the same inspected file
38
+ # simultaneously, the only problem we run in to is a competition who gets
39
+ # to write to the final file. The contents are the same, so no corruption
40
+ # of data should occur.
41
+ FileUtils.mv(preliminary_path, @path)
42
+ end
43
+
44
+ # Remove old files so that the cache doesn't grow too big. When the
45
+ # threshold MaxFilesInCache has been exceeded, the oldest 50% all the files
46
+ # in the cache are removed. The reason for removing so much is that
47
+ # cleaning should be done relatively seldom, since there is a slight risk
48
+ # that some other RuboCop process was just about to read the file, when
49
+ # there's parallel execution and the cache is shared.
50
+ def self.cleanup(config_store, verbose, cache_root = nil)
51
+ return if inhibit_cleanup # OPTIMIZE: For faster testing
52
+ cache_root ||= cache_root(config_store)
53
+ return unless File.exist?(cache_root)
54
+
55
+ files, dirs = Find.find(cache_root).partition { |path| File.file?(path) }
56
+ if files.length > config_store.for('.')['AllCops']['MaxFilesInCache'] &&
57
+ files.length > 1
58
+ # Add 1 to half the number of files, so that we remove the file if
59
+ # there's only 1 left.
60
+ remove_count = 1 + files.length / 2
61
+ if verbose
62
+ puts "Removing the #{remove_count} oldest files from #{cache_root}"
63
+ end
64
+ sorted = files.sort_by { |path| File.mtime(path) }
65
+ begin
66
+ sorted[0, remove_count].each { |path| File.delete(path) }
67
+ dirs.each { |dir| Dir.rmdir(dir) if Dir["#{dir}/*"].empty? }
68
+ rescue Errno::ENOENT
69
+ # This can happen if parallel RuboCop invocations try to remove the
70
+ # same files. No problem.
71
+ puts $ERROR_INFO if verbose
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def self.cache_root(config_store)
79
+ root = config_store.for('.')['AllCops']['CacheRootDirectory']
80
+ root = Dir.tmpdir if root == '/tmp'
81
+ File.join(root, 'rubocop_cache')
82
+ end
83
+
84
+ def file_checksum(file, config_store)
85
+ Digest::MD5.hexdigest(Dir.pwd + file + IO.read(file) +
86
+ config_store.for(file).to_s)
87
+ rescue Errno::ENOENT
88
+ # Spurious files that come and go should not cause a crash, at least not
89
+ # here.
90
+ '_'
91
+ end
92
+
93
+ class << self
94
+ attr_accessor :source_checksum, :inhibit_cleanup
95
+ end
96
+
97
+ # The checksum of the rubocop program running the inspection.
98
+ def rubocop_checksum
99
+ ResultCache.source_checksum ||=
100
+ begin
101
+ lib_root = File.join(File.dirname(__FILE__), '..')
102
+ bin_root = File.join(lib_root, '..', 'bin')
103
+ source = Find.find(lib_root, bin_root).sort.map do |path|
104
+ IO.read(path) if File.file?(path)
105
+ end
106
+ Digest::MD5.hexdigest(source.join)
107
+ end
108
+ end
109
+
110
+ NON_CHANGING = [:color, :format, :formatters, :out, :debug, :fail_level,
111
+ :cache, :fail_fast]
112
+
113
+ # Return the options given at invocation, minus the ones that have no
114
+ # effect on which offenses and disabled line ranges are found, and thus
115
+ # don't affect caching.
116
+ def relevant_options(options)
117
+ options = options.reject { |key, _| NON_CHANGING.include?(key) }
118
+ options.to_s.gsub(/[^a-z]+/i, '_')
119
+ end
120
+ end
121
+ end