You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
6.7 KiB
191 lines
6.7 KiB
/** |
|
* @fileoverview Utility for caching lint results. |
|
* @author Kevin Partington |
|
*/ |
|
"use strict"; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Requirements |
|
//----------------------------------------------------------------------------- |
|
|
|
const assert = require("assert"); |
|
const fs = require("fs"); |
|
const fileEntryCache = require("file-entry-cache"); |
|
const stringify = require("json-stable-stringify-without-jsonify"); |
|
const pkg = require("../../package.json"); |
|
const hash = require("./hash"); |
|
|
|
const debug = require("debug")("eslint:lint-result-cache"); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Helpers |
|
//----------------------------------------------------------------------------- |
|
|
|
const configHashCache = new WeakMap(); |
|
const nodeVersion = process && process.version; |
|
|
|
const validCacheStrategies = ["metadata", "content"]; |
|
const invalidCacheStrategyErrorMessage = `Cache strategy must be one of: ${validCacheStrategies |
|
.map(strategy => `"${strategy}"`) |
|
.join(", ")}`; |
|
|
|
/** |
|
* Tests whether a provided cacheStrategy is valid |
|
* @param {string} cacheStrategy The cache strategy to use |
|
* @returns {boolean} true if `cacheStrategy` is one of `validCacheStrategies`; false otherwise |
|
*/ |
|
function isValidCacheStrategy(cacheStrategy) { |
|
return ( |
|
validCacheStrategies.indexOf(cacheStrategy) !== -1 |
|
); |
|
} |
|
|
|
/** |
|
* Calculates the hash of the config |
|
* @param {ConfigArray} config The config. |
|
* @returns {string} The hash of the config |
|
*/ |
|
function hashOfConfigFor(config) { |
|
if (!configHashCache.has(config)) { |
|
configHashCache.set(config, hash(`${pkg.version}_${nodeVersion}_${stringify(config)}`)); |
|
} |
|
|
|
return configHashCache.get(config); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Public Interface |
|
//----------------------------------------------------------------------------- |
|
|
|
/** |
|
* Lint result cache. This wraps around the file-entry-cache module, |
|
* transparently removing properties that are difficult or expensive to |
|
* serialize and adding them back in on retrieval. |
|
*/ |
|
class LintResultCache { |
|
|
|
/** |
|
* Creates a new LintResultCache instance. |
|
* @param {string} cacheFileLocation The cache file location. |
|
* @param {"metadata" | "content"} cacheStrategy The cache strategy to use. |
|
*/ |
|
constructor(cacheFileLocation, cacheStrategy) { |
|
assert(cacheFileLocation, "Cache file location is required"); |
|
assert(cacheStrategy, "Cache strategy is required"); |
|
assert( |
|
isValidCacheStrategy(cacheStrategy), |
|
invalidCacheStrategyErrorMessage |
|
); |
|
|
|
debug(`Caching results to ${cacheFileLocation}`); |
|
|
|
const useChecksum = cacheStrategy === "content"; |
|
|
|
debug( |
|
`Using "${cacheStrategy}" strategy to detect changes` |
|
); |
|
|
|
this.fileEntryCache = fileEntryCache.create( |
|
cacheFileLocation, |
|
void 0, |
|
useChecksum |
|
); |
|
this.cacheFileLocation = cacheFileLocation; |
|
} |
|
|
|
/** |
|
* Retrieve cached lint results for a given file path, if present in the |
|
* cache. If the file is present and has not been changed, rebuild any |
|
* missing result information. |
|
* @param {string} filePath The file for which to retrieve lint results. |
|
* @param {ConfigArray} config The config of the file. |
|
* @returns {Object|null} The rebuilt lint results, or null if the file is |
|
* changed or not in the filesystem. |
|
*/ |
|
getCachedLintResults(filePath, config) { |
|
|
|
/* |
|
* Cached lint results are valid if and only if: |
|
* 1. The file is present in the filesystem |
|
* 2. The file has not changed since the time it was previously linted |
|
* 3. The ESLint configuration has not changed since the time the file |
|
* was previously linted |
|
* If any of these are not true, we will not reuse the lint results. |
|
*/ |
|
const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath); |
|
const hashOfConfig = hashOfConfigFor(config); |
|
const changed = |
|
fileDescriptor.changed || |
|
fileDescriptor.meta.hashOfConfig !== hashOfConfig; |
|
|
|
if (fileDescriptor.notFound) { |
|
debug(`File not found on the file system: ${filePath}`); |
|
return null; |
|
} |
|
|
|
if (changed) { |
|
debug(`Cache entry not found or no longer valid: ${filePath}`); |
|
return null; |
|
} |
|
|
|
// If source is present but null, need to reread the file from the filesystem. |
|
if ( |
|
fileDescriptor.meta.results && |
|
fileDescriptor.meta.results.source === null |
|
) { |
|
debug(`Rereading cached result source from filesystem: ${filePath}`); |
|
fileDescriptor.meta.results.source = fs.readFileSync(filePath, "utf-8"); |
|
} |
|
|
|
return fileDescriptor.meta.results; |
|
} |
|
|
|
/** |
|
* Set the cached lint results for a given file path, after removing any |
|
* information that will be both unnecessary and difficult to serialize. |
|
* Avoids caching results with an "output" property (meaning fixes were |
|
* applied), to prevent potentially incorrect results if fixes are not |
|
* written to disk. |
|
* @param {string} filePath The file for which to set lint results. |
|
* @param {ConfigArray} config The config of the file. |
|
* @param {Object} result The lint result to be set for the file. |
|
* @returns {void} |
|
*/ |
|
setCachedLintResults(filePath, config, result) { |
|
if (result && Object.prototype.hasOwnProperty.call(result, "output")) { |
|
return; |
|
} |
|
|
|
const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath); |
|
|
|
if (fileDescriptor && !fileDescriptor.notFound) { |
|
debug(`Updating cached result: ${filePath}`); |
|
|
|
// Serialize the result, except that we want to remove the file source if present. |
|
const resultToSerialize = Object.assign({}, result); |
|
|
|
/* |
|
* Set result.source to null. |
|
* In `getCachedLintResults`, if source is explicitly null, we will |
|
* read the file from the filesystem to set the value again. |
|
*/ |
|
if (Object.prototype.hasOwnProperty.call(resultToSerialize, "source")) { |
|
resultToSerialize.source = null; |
|
} |
|
|
|
fileDescriptor.meta.results = resultToSerialize; |
|
fileDescriptor.meta.hashOfConfig = hashOfConfigFor(config); |
|
} |
|
} |
|
|
|
/** |
|
* Persists the in-memory cache to disk. |
|
* @returns {void} |
|
*/ |
|
reconcile() { |
|
debug(`Persisting cached results: ${this.cacheFileLocation}`); |
|
this.fileEntryCache.reconcile(); |
|
} |
|
} |
|
|
|
module.exports = LintResultCache;
|
|
|