• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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