4
0

FileCache.mjs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { readFileSync, rmSync } from 'node:fs';
  2. import process from 'node:process';
  3. import { resolve } from 'node:path';
  4. import createDebug from 'debug';
  5. import fileEntryCache from 'file-entry-cache';
  6. import {
  7. CACHE_STRATEGY_CONTENT,
  8. CACHE_STRATEGY_METADATA,
  9. DEFAULT_CACHE_LOCATION,
  10. DEFAULT_CACHE_STRATEGY,
  11. } from '../constants.mjs';
  12. import hash from './hash.mjs';
  13. import resolveFilePath from './resolveFilePath.mjs';
  14. const debug = createDebug('stylelint:file-cache');
  15. const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
  16. export default class FileCache {
  17. constructor(
  18. cacheLocation = DEFAULT_CACHE_LOCATION,
  19. cacheStrategy = DEFAULT_CACHE_STRATEGY,
  20. cwd = process.cwd(),
  21. ) {
  22. if (![CACHE_STRATEGY_METADATA, CACHE_STRATEGY_CONTENT].includes(cacheStrategy)) {
  23. throw new Error(
  24. `"${cacheStrategy}" cache strategy is unsupported. Specify either "${CACHE_STRATEGY_METADATA}" or "${CACHE_STRATEGY_CONTENT}"`,
  25. );
  26. }
  27. const cacheFile = resolve(resolveFilePath(cacheLocation, cwd, `.stylelintcache_${hash(cwd)}`));
  28. const useCheckSum = cacheStrategy === CACHE_STRATEGY_CONTENT;
  29. debug(`Cache file is created at ${cacheFile}`);
  30. try {
  31. this._fileCache = fileEntryCache.createFromFile(cacheFile, useCheckSum, undefined);
  32. } catch {
  33. debug(`Cache file might be corrupt, attempting to remove and recreate the cache file`);
  34. rmSync(cacheFile, { force: true });
  35. this._fileCache = fileEntryCache.createFromFile(cacheFile, useCheckSum, undefined);
  36. }
  37. this._hashOfConfig = '';
  38. this._useCheckSum = useCheckSum;
  39. }
  40. /**
  41. * @param {import('stylelint').Config} config
  42. */
  43. calcHashOfConfig(config) {
  44. if (this._hashOfConfig) return;
  45. const stylelintVersion = pkg.version;
  46. const configString = JSON.stringify(config || {});
  47. this._hashOfConfig = hash(`${stylelintVersion}_${configString}`);
  48. }
  49. /**
  50. * @param {string} absoluteFilepath
  51. * @returns {boolean}
  52. */
  53. hasFileChanged(absoluteFilepath) {
  54. // Get file descriptor compares current metadata against cached
  55. // one and stores the result to "changed" prop.w
  56. /** @type {import('file-entry-cache').FileDescriptorMeta | undefined} */
  57. const metaCache = this._fileCache.cache.getKey(this._fileCache.createFileKey(absoluteFilepath));
  58. const descriptor = this._fileCache.getFileDescriptor(absoluteFilepath);
  59. /** @type {{ hashOfConfig?: string; }} */
  60. const metadata = (descriptor.meta.data ??= {});
  61. const configChanged = metadata.hashOfConfig !== this._hashOfConfig;
  62. let changed;
  63. if (this._useCheckSum) {
  64. changed = configChanged || !metaCache?.hash || metaCache.hash !== descriptor.meta.hash;
  65. } else {
  66. changed = configChanged || Boolean(descriptor.changed);
  67. }
  68. if (!changed) {
  69. debug(`Skip linting ${absoluteFilepath}. File hasn't changed.`);
  70. }
  71. // Mutate file descriptor object and store config hash to each file.
  72. // Running lint with different config should invalidate the cache.
  73. if (metadata.hashOfConfig !== this._hashOfConfig) {
  74. metadata.hashOfConfig = this._hashOfConfig;
  75. }
  76. return changed;
  77. }
  78. reconcile() {
  79. this._fileCache.reconcile();
  80. }
  81. destroy() {
  82. this._fileCache.destroy();
  83. }
  84. /**
  85. * @param {string} absoluteFilepath
  86. */
  87. removeEntry(absoluteFilepath) {
  88. this._fileCache.removeEntry(absoluteFilepath);
  89. }
  90. }