--- /dev/null
+/**
+ * @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;