lint-result-cache.js 5.1 KB

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