| import { readFileSync, rmSync } from 'node:fs'; |
| import process from 'node:process'; |
| import { resolve } from 'node:path'; |
| |
| import createDebug from 'debug'; |
| import fileEntryCache from 'file-entry-cache'; |
| |
| import { |
| CACHE_STRATEGY_CONTENT, |
| CACHE_STRATEGY_METADATA, |
| DEFAULT_CACHE_LOCATION, |
| DEFAULT_CACHE_STRATEGY, |
| } from '../constants.mjs'; |
| import hash from './hash.mjs'; |
| import resolveFilePath from './resolveFilePath.mjs'; |
| |
| const debug = createDebug('stylelint:file-cache'); |
| |
| const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')); |
| |
| export default class FileCache { |
| constructor( |
| cacheLocation = DEFAULT_CACHE_LOCATION, |
| cacheStrategy = DEFAULT_CACHE_STRATEGY, |
| cwd = process.cwd(), |
| ) { |
| if (![CACHE_STRATEGY_METADATA, CACHE_STRATEGY_CONTENT].includes(cacheStrategy)) { |
| throw new Error( |
| `"${cacheStrategy}" cache strategy is unsupported. Specify either "${CACHE_STRATEGY_METADATA}" or "${CACHE_STRATEGY_CONTENT}"`, |
| ); |
| } |
| |
| const cacheFile = resolve(resolveFilePath(cacheLocation, cwd, `.stylelintcache_${hash(cwd)}`)); |
| const useCheckSum = cacheStrategy === CACHE_STRATEGY_CONTENT; |
| |
| debug(`Cache file is created at ${cacheFile}`); |
| |
| try { |
| this._fileCache = fileEntryCache.createFromFile(cacheFile, useCheckSum, undefined); |
| } catch { |
| debug(`Cache file might be corrupt, attempting to remove and recreate the cache file`); |
| |
| rmSync(cacheFile, { force: true }); |
| this._fileCache = fileEntryCache.createFromFile(cacheFile, useCheckSum, undefined); |
| } |
| |
| this._hashOfConfig = ''; |
| this._useCheckSum = useCheckSum; |
| } |
| |
| /** |
| * @param {import('stylelint').Config} config |
| */ |
| calcHashOfConfig(config) { |
| if (this._hashOfConfig) return; |
| |
| const stylelintVersion = pkg.version; |
| const configString = JSON.stringify(config || {}); |
| |
| this._hashOfConfig = hash(`${stylelintVersion}_${configString}`); |
| } |
| |
| /** |
| * @param {string} absoluteFilepath |
| * @returns {boolean} |
| */ |
| hasFileChanged(absoluteFilepath) { |
| // Get file descriptor compares current metadata against cached |
| // one and stores the result to "changed" prop.w |
| |
| /** @type {import('file-entry-cache').FileDescriptorMeta | undefined} */ |
| const metaCache = this._fileCache.cache.getKey(this._fileCache.createFileKey(absoluteFilepath)); |
| const descriptor = this._fileCache.getFileDescriptor(absoluteFilepath); |
| |
| /** @type {{ hashOfConfig?: string; }} */ |
| const metadata = (descriptor.meta.data ??= {}); |
| |
| const configChanged = metadata.hashOfConfig !== this._hashOfConfig; |
| |
| let changed; |
| |
| if (this._useCheckSum) { |
| changed = configChanged || !metaCache?.hash || metaCache.hash !== descriptor.meta.hash; |
| } else { |
| changed = configChanged || Boolean(descriptor.changed); |
| } |
| |
| if (!changed) { |
| debug(`Skip linting ${absoluteFilepath}. File hasn't changed.`); |
| } |
| |
| // Mutate file descriptor object and store config hash to each file. |
| // Running lint with different config should invalidate the cache. |
| if (metadata.hashOfConfig !== this._hashOfConfig) { |
| metadata.hashOfConfig = this._hashOfConfig; |
| } |
| |
| return changed; |
| } |
| |
| reconcile() { |
| this._fileCache.reconcile(); |
| } |
| |
| destroy() { |
| this._fileCache.destroy(); |
| } |
| |
| /** |
| * @param {string} absoluteFilepath |
| */ |
| removeEntry(absoluteFilepath) { |
| this._fileCache.removeEntry(absoluteFilepath); |
| } |
| } |