UNPKG

cspell

Version:

A Spelling Checker for Code!

214 lines 7.28 kB
import assert from 'node:assert'; import * as crypto from 'node:crypto'; import * as fs from 'node:fs'; import { dirname, isAbsolute as isAbsolutePath, relative as relativePath, resolve as resolvePath } from 'node:path'; import { readFileInfo } from '../../util/fileHelper.js'; import { createFromFile, normalizePath } from './fileEntryCache.js'; import { ShallowObjectCollection } from './ObjectCollection.js'; const cacheDataKeys = { v: 'v', r: 'r', d: 'd', }; /** * Meta Data Version is used to detect if the structure of the meta data has changed. * This is used in combination with the Suffix and the version of CSpell. */ const META_DATA_BASE_VERSION = '1'; const META_DATA_VERSION_SUFFIX = '-' + META_DATA_BASE_VERSION + '-' + Object.keys(cacheDataKeys).join('|'); /** * Caches cspell results on disk */ export class DiskCache { useCheckSum; cspellVersion; useUniversalCache; cacheFileLocation; cacheDir; fileEntryCache; dependencyCache = new Map(); dependencyCacheTree = {}; objectCollection = new ShallowObjectCollection(); ocCacheFileResult = new ShallowObjectCollection(); version; constructor(cacheFileLocation, useCheckSum, cspellVersion, useUniversalCache) { this.useCheckSum = useCheckSum; this.cspellVersion = cspellVersion; this.useUniversalCache = useUniversalCache; this.cacheFileLocation = resolvePath(cacheFileLocation); this.cacheDir = dirname(this.cacheFileLocation); this.fileEntryCache = createFromFile(this.cacheFileLocation, useCheckSum, useUniversalCache); this.version = calcVersion(cspellVersion); } async getCachedLintResults(filename) { filename = normalizePath(filename); const fileDescriptor = this.fileEntryCache.getFileDescriptor(filename); const meta = fileDescriptor.meta; const data = meta?.data; const result = data?.r; const versionMatches = this.version === data?.v; // Cached lint results are valid if and only if: // 1. The file is present in the filesystem // 2. The file has not changed since the time it was previously linted // 3. The CSpell configuration has not changed since the time the file was previously linted // If any of these are not true, we will not reuse the lint results. if (fileDescriptor.notFound || fileDescriptor.changed || !meta || !result || !versionMatches || !this.checkDependencies(data.d)) { return undefined; } const dd = { ...data }; if (dd.d) { dd.d = setTreeEntry(this.dependencyCacheTree, dd.d); } dd.r = dd.r && this.normalizeResult(dd.r); meta.data = this.objectCollection.get(dd); // Skip reading empty files and files without lint error const hasErrors = !!result && (result.errors > 0 || result.configErrors > 0 || result.issues.length > 0); const cached = true; const shouldReadFile = cached && hasErrors; return { ...result, elapsedTimeMs: undefined, fileInfo: shouldReadFile ? await readFileInfo(filename) : { filename }, cached, }; } setCachedLintResults({ fileInfo, elapsedTimeMs: _, cached: __, ...result }, dependsUponFiles) { const fileDescriptor = this.fileEntryCache.getFileDescriptor(fileInfo.filename); const meta = fileDescriptor.meta; if (fileDescriptor.notFound || !meta) { return; } const data = this.objectCollection.get({ v: this.version, r: this.normalizeResult(result), d: this.calcDependencyHashes(dependsUponFiles), }); meta.data = data; } reconcile() { this.fileEntryCache.reconcile(); } reset() { this.fileEntryCache.destroy(); this.dependencyCache.clear(); this.dependencyCacheTree = {}; this.objectCollection = new ShallowObjectCollection(); this.ocCacheFileResult = new ShallowObjectCollection(); } normalizeResult(result) { const { issues, processed, errors, configErrors, ...rest } = result; if (!Object.keys(rest).length) { return this.ocCacheFileResult.get(result); } return this.ocCacheFileResult.get({ issues, processed, errors, configErrors }); } calcDependencyHashes(dependsUponFiles) { dependsUponFiles.sort(); const c = getTreeEntry(this.dependencyCacheTree, dependsUponFiles); if (c?.d) { return c.d; } const dependencies = dependsUponFiles.map((f) => this.getDependency(f)); return setTreeEntry(this.dependencyCacheTree, dependencies); } checkDependency(dep) { const depFile = this.resolveFile(dep.f); const cDep = this.dependencyCache.get(depFile); if (cDep && compDep(dep, cDep)) return true; if (cDep) return false; const d = this.getFileDep(depFile); if (compDep(dep, d)) { this.dependencyCache.set(depFile, dep); return true; } this.dependencyCache.set(depFile, d); return false; } getDependency(file) { const dep = this.dependencyCache.get(file); if (dep) return dep; const d = this.getFileDep(file); this.dependencyCache.set(file, d); return d; } getFileDep(file) { assert(isAbsolutePath(file), `Dependency must be absolute "${file}"`); const f = this.toRelFile(file); let h; try { const buffer = fs.readFileSync(file); h = this.getHash(buffer); } catch { return { f }; } return { f, h }; } checkDependencies(dependencies) { if (!dependencies) return false; for (const dep of dependencies) { if (!this.checkDependency(dep)) { return false; } } return true; } getHash(buffer) { return crypto.createHash('md5').update(buffer).digest('hex'); } resolveFile(file) { return normalizePath(resolvePath(this.cacheDir, file)); } toRelFile(file) { return normalizePath(this.useUniversalCache ? relativePath(this.cacheDir, file) : file); } } function getTreeEntry(tree, keys) { let r = tree; for (const k of keys) { r = r.c?.get(k); if (!r) return r; } return r; } function setTreeEntry(tree, deps, update = false) { let r = tree; for (const d of deps) { const k = d.f; if (!r.c) { r.c = new Map(); } const cn = r.c.get(k); const n = cn ?? {}; if (!cn) { r.c.set(k, n); } r = n; } let d = r.d; if (!d || (r.d && update)) { r.d = deps; d = deps; } return d; } function compDep(a, b) { return a.f === b.f && a.h === b.h; } function calcVersion(version) { return version + META_DATA_VERSION_SUFFIX; } export const __testing__ = { calcVersion, }; //# sourceMappingURL=DiskCache.js.map