Class: RuboCop::ResultCache

Inherits:
Object
  • Object
show all
Defined in:
lib/rubocop/result_cache.rb

Overview

Provides functionality for caching rubocop runs.

Constant Summary collapse

NON_CHANGING =
%i[color format formatters out debug fail_level auto_correct
cache fail_fast stdin parallel].freeze

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file, options, config_store, cache_root = nil) ⇒ ResultCache

Returns a new instance of ResultCache


80
81
82
83
84
85
86
87
88
# File 'lib/rubocop/result_cache.rb', line 80

def initialize(file, options, config_store, cache_root = nil)
  cache_root ||= ResultCache.cache_root(config_store)
  @allow_symlinks_in_cache_location =
    ResultCache.allow_symlinks_in_cache_location?(config_store)
  @path = File.join(cache_root, rubocop_checksum,
                    relevant_options_digest(options),
                    file_checksum(file, config_store))
  @cached_data = CachedData.new(file)
end

Class Attribute Details

.inhibit_cleanupObject

Returns the value of attribute inhibit_cleanup


158
159
160
# File 'lib/rubocop/result_cache.rb', line 158

def inhibit_cleanup
  @inhibit_cleanup
end

.source_checksumObject

Returns the value of attribute source_checksum


158
159
160
# File 'lib/rubocop/result_cache.rb', line 158

def source_checksum
  @source_checksum
end

Class Method Details

Returns:

  • (Boolean)

76
77
78
# File 'lib/rubocop/result_cache.rb', line 76

def self.allow_symlinks_in_cache_location?(config_store)
  config_store.for('.').for_all_cops['AllowSymlinksInCacheRootDirectory']
end

.cache_root(config_store) ⇒ Object


64
65
66
67
68
69
70
71
72
73
74
# File 'lib/rubocop/result_cache.rb', line 64

def self.cache_root(config_store)
  root = config_store.for('.').for_all_cops['CacheRootDirectory']
  root ||= if ENV.key?('XDG_CACHE_HOME')
             # Include user ID in the path to make sure the user has write
             # access.
             File.join(ENV['XDG_CACHE_HOME'], Process.uid.to_s)
           else
             File.join(ENV['HOME'], '.cache')
           end
  File.join(root, 'rubocop_cache')
end

.cleanup(config_store, verbose, cache_root = nil) ⇒ Object

Remove old files so that the cache doesn't grow too big. When the threshold MaxFilesInCache has been exceeded, the oldest 50% of all the files in the cache are removed. The reason for removing so much is that cleaning should be done relatively seldom, since there is a slight risk that some other RuboCop process was just about to read the file, when there's parallel execution and the cache is shared.


19
20
21
22
23
24
25
26
27
28
29
# File 'lib/rubocop/result_cache.rb', line 19

def self.cleanup(config_store, verbose, cache_root = nil)
  return if inhibit_cleanup # OPTIMIZE: For faster testing

  cache_root ||= cache_root(config_store)
  return unless File.exist?(cache_root)

  files, dirs = Find.find(cache_root).partition { |path| File.file?(path) }
  return unless requires_file_removal?(files.length, config_store)

  remove_oldest_files(files, dirs, cache_root, verbose)
end

Instance Method Details

#loadObject


94
95
96
# File 'lib/rubocop/result_cache.rb', line 94

def load
  @cached_data.from_json(IO.read(@path, encoding: Encoding::UTF_8))
end

#save(offenses) ⇒ Object


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/rubocop/result_cache.rb', line 98

def save(offenses)
  dir = File.dirname(@path)

  begin
    FileUtils.mkdir_p(dir)
  rescue Errno::EACCES => e
    warn "Couldn't create cache directory. Continuing without cache."\
         "\n  #{e.message}"
    return
  end

  preliminary_path = "#{@path}_#{rand(1_000_000_000)}"
  # RuboCop must be in control of where its cached data is stored. A
  # symbolic link anywhere in the cache directory tree can be an
  # indication that a symlink attack is being waged.
  return if symlink_protection_triggered?(dir)

  File.open(preliminary_path, 'w', encoding: Encoding::UTF_8) do |f|
    f.write(@cached_data.to_json(offenses))
  end
  # The preliminary path is used so that if there are multiple RuboCop
  # processes trying to save data for the same inspected file
  # simultaneously, the only problem we run in to is a competition who gets
  # to write to the final file. The contents are the same, so no corruption
  # of data should occur.
  FileUtils.mv(preliminary_path, @path)
end

#valid?Boolean

Returns:

  • (Boolean)

90
91
92
# File 'lib/rubocop/result_cache.rb', line 90

def valid?
  File.exist?(@path)
end