UNPKG

knip

Version:

Find unused files, dependencies and exports in your TypeScript and JavaScript projects

110 lines (109 loc) 3.62 kB
import fs from 'node:fs'; import path from 'node:path'; import { timerify } from './Performance.js'; import { debugLog } from './debug.js'; import { isDirectory, isFile } from './fs.js'; import { cwd, dirname, isAbsolute, resolve } from './path.js'; import { deserialize, serialize } from './serialize.js'; const createCache = (filePath) => { try { return deserialize(fs.readFileSync(filePath)); } catch (_err) { debugLog('*', `Error reading cache from ${filePath}`); } }; const create = timerify(createCache); export class FileEntryCache { filePath; cache = new Map(); normalizedEntries = new Map(); constructor(cacheId, _path) { this.filePath = isAbsolute(_path) ? path.resolve(_path, cacheId) : path.resolve(cwd, _path, cacheId); if (isFile(this.filePath)) this.cache = create(this.filePath); this.removeNotFoundFiles(); } removeNotFoundFiles() { for (const filePath of this.normalizedEntries.keys()) { try { fs.statSync(filePath); } catch (error) { if (error.code === 'ENOENT') this.cache.delete(filePath); } } } getFileDescriptor(filePath) { let fstat; try { if (!isAbsolute(filePath)) filePath = resolve(filePath); fstat = fs.statSync(filePath); } catch (error) { this.removeEntry(filePath); return { key: filePath, notFound: true, err: error }; } return this._getFileDescriptorUsingMtimeAndSize(filePath, fstat); } _getFileDescriptorUsingMtimeAndSize(filePath, fstat) { let meta = this.cache.get(filePath); const cacheExists = Boolean(meta); const cSize = fstat.size; const cTime = fstat.mtime.getTime(); let isDifferentDate; let isDifferentSize; if (meta) { isDifferentDate = cTime !== meta.mtime; isDifferentSize = cSize !== meta.size; } else { meta = { size: cSize, mtime: cTime }; } const fd = { key: filePath, changed: !cacheExists || isDifferentDate || isDifferentSize, meta, }; this.normalizedEntries.set(filePath, fd); return fd; } removeEntry(entryName) { if (!isAbsolute(entryName)) entryName = resolve(cwd, entryName); this.normalizedEntries.delete(entryName); this.cache.delete(entryName); } _getMetaForFileUsingMtimeAndSize(cacheEntry) { const stat = fs.statSync(cacheEntry.key); const meta = Object.assign(cacheEntry.meta ?? {}, { size: stat.size, mtime: stat.mtime.getTime(), }); return meta; } reconcile() { this.removeNotFoundFiles(); for (const [entryName, cacheEntry] of this.normalizedEntries.entries()) { try { const meta = this._getMetaForFileUsingMtimeAndSize(cacheEntry); this.cache.set(entryName, meta); } catch (error) { if (error.code !== 'ENOENT') throw error; } } try { const dir = dirname(this.filePath); if (!isDirectory(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(this.filePath, serialize(this.cache)); } catch (_err) { debugLog('*', `Error writing cache to ${this.filePath}`); } } }