 c177363a19
			
		
	
	c177363a19
	
	
	
		
			
			🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			204 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			204 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @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.includes(cacheStrategy)
 | |
|     );
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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;
 | |
|         }
 | |
| 
 | |
|         const cachedResults = fileDescriptor.meta.results;
 | |
| 
 | |
|         // Just in case, not sure if this can ever happen.
 | |
|         if (!cachedResults) {
 | |
|             return cachedResults;
 | |
|         }
 | |
| 
 | |
|         /*
 | |
|          * Shallow clone the object to ensure that any properties added or modified afterwards
 | |
|          * will not be accidentally stored in the cache file when `reconcile()` is called.
 | |
|          * https://github.com/eslint/eslint/issues/13507
 | |
|          * All intentional changes to the cache file must be done through `setCachedLintResults()`.
 | |
|          */
 | |
|         const results = { ...cachedResults };
 | |
| 
 | |
|         // If source is present but null, need to reread the file from the filesystem.
 | |
|         if (results.source === null) {
 | |
|             debug(`Rereading cached result source from filesystem: ${filePath}`);
 | |
|             results.source = fs.readFileSync(filePath, "utf-8");
 | |
|         }
 | |
| 
 | |
|         return 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;
 |