1/** 2 * @fileoverview Utility for caching lint results. 3 * @author Kevin Partington 4 */ 5"use strict"; 6 7//----------------------------------------------------------------------------- 8// Requirements 9//----------------------------------------------------------------------------- 10 11const assert = require("assert"); 12const fs = require("fs"); 13const fileEntryCache = require("file-entry-cache"); 14const stringify = require("json-stable-stringify-without-jsonify"); 15const pkg = require("../../package.json"); 16const hash = require("./hash"); 17 18//----------------------------------------------------------------------------- 19// Helpers 20//----------------------------------------------------------------------------- 21 22const configHashCache = new WeakMap(); 23const nodeVersion = process && process.version; 24 25/** 26 * Calculates the hash of the config 27 * @param {ConfigArray} config The config. 28 * @returns {string} The hash of the config 29 */ 30function hashOfConfigFor(config) { 31 if (!configHashCache.has(config)) { 32 configHashCache.set(config, hash(`${pkg.version}_${nodeVersion}_${stringify(config)}`)); 33 } 34 35 return configHashCache.get(config); 36} 37 38//----------------------------------------------------------------------------- 39// Public Interface 40//----------------------------------------------------------------------------- 41 42/** 43 * Lint result cache. This wraps around the file-entry-cache module, 44 * transparently removing properties that are difficult or expensive to 45 * serialize and adding them back in on retrieval. 46 */ 47class LintResultCache { 48 49 /** 50 * Creates a new LintResultCache instance. 51 * @param {string} cacheFileLocation The cache file location. 52 * configuration lookup by file path). 53 */ 54 constructor(cacheFileLocation) { 55 assert(cacheFileLocation, "Cache file location is required"); 56 57 this.fileEntryCache = fileEntryCache.create(cacheFileLocation); 58 } 59 60 /** 61 * Retrieve cached lint results for a given file path, if present in the 62 * cache. If the file is present and has not been changed, rebuild any 63 * missing result information. 64 * @param {string} filePath The file for which to retrieve lint results. 65 * @param {ConfigArray} config The config of the file. 66 * @returns {Object|null} The rebuilt lint results, or null if the file is 67 * changed or not in the filesystem. 68 */ 69 getCachedLintResults(filePath, config) { 70 71 /* 72 * Cached lint results are valid if and only if: 73 * 1. The file is present in the filesystem 74 * 2. The file has not changed since the time it was previously linted 75 * 3. The ESLint configuration has not changed since the time the file 76 * was previously linted 77 * If any of these are not true, we will not reuse the lint results. 78 */ 79 80 const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath); 81 const hashOfConfig = hashOfConfigFor(config); 82 const changed = fileDescriptor.changed || fileDescriptor.meta.hashOfConfig !== hashOfConfig; 83 84 if (fileDescriptor.notFound || changed) { 85 return null; 86 } 87 88 // If source is present but null, need to reread the file from the filesystem. 89 if (fileDescriptor.meta.results && fileDescriptor.meta.results.source === null) { 90 fileDescriptor.meta.results.source = fs.readFileSync(filePath, "utf-8"); 91 } 92 93 return fileDescriptor.meta.results; 94 } 95 96 /** 97 * Set the cached lint results for a given file path, after removing any 98 * information that will be both unnecessary and difficult to serialize. 99 * Avoids caching results with an "output" property (meaning fixes were 100 * applied), to prevent potentially incorrect results if fixes are not 101 * written to disk. 102 * @param {string} filePath The file for which to set lint results. 103 * @param {ConfigArray} config The config of the file. 104 * @param {Object} result The lint result to be set for the file. 105 * @returns {void} 106 */ 107 setCachedLintResults(filePath, config, result) { 108 if (result && Object.prototype.hasOwnProperty.call(result, "output")) { 109 return; 110 } 111 112 const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath); 113 114 if (fileDescriptor && !fileDescriptor.notFound) { 115 116 // Serialize the result, except that we want to remove the file source if present. 117 const resultToSerialize = Object.assign({}, result); 118 119 /* 120 * Set result.source to null. 121 * In `getCachedLintResults`, if source is explicitly null, we will 122 * read the file from the filesystem to set the value again. 123 */ 124 if (Object.prototype.hasOwnProperty.call(resultToSerialize, "source")) { 125 resultToSerialize.source = null; 126 } 127 128 fileDescriptor.meta.results = resultToSerialize; 129 fileDescriptor.meta.hashOfConfig = hashOfConfigFor(config); 130 } 131 } 132 133 /** 134 * Persists the in-memory cache to disk. 135 * @returns {void} 136 */ 137 reconcile() { 138 this.fileEntryCache.reconcile(); 139 } 140} 141 142module.exports = LintResultCache; 143