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