UNPKG

knip

Version:

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

139 lines (138 loc) 6.28 kB
import { readFileSync } from 'node:fs'; import { invalidateCache } from "../graph-explorer/cache.js"; import { debugLog } from "./debug.js"; import { isFile } from "./fs.js"; import { updateImportMap } from "./module-graph.js"; import { toAbsolute, toPosix, toRelative } from "./path.js"; import { clearResolverCache } from "./resolve.js"; const createUpdate = (options) => { const duration = performance.now() - options.startTime; const mem = process.memoryUsage().heapUsed; return { duration, mem }; }; export const getSessionHandler = async (options, { analyzedFiles, analyzeSourceFile, chief, collector, analyze, principal, graph, isIgnored, onFileChange, unreferencedFiles, entryPaths, }) => { const handleFileChanges = async (changes) => { const startTime = performance.now(); const added = new Set(); const deleted = new Set(); const modified = new Set(); for (const change of changes) { const filePath = toAbsolute(change.filePath, options.cwd); const relativePath = toRelative(change.filePath, options.cwd); if (isIgnored(filePath)) { debugLog('*', `ignoring ${change.type} ${relativePath}`); continue; } const workspace = chief.findWorkspaceByFilePath(filePath); if (!workspace) continue; switch (change.type) { case 'added': principal.addProjectPath(filePath); principal.deletedFiles.delete(filePath); if (principal.projectPaths.has(filePath)) added.add(filePath); debugLog(workspace.name, `Watcher: + ${relativePath}`); break; case 'deleted': deleted.add(filePath); analyzedFiles.delete(filePath); principal.removeProjectPath(filePath); debugLog(workspace.name, `Watcher: - ${relativePath}`); break; default: { const cached = principal.fileManager.sourceTextCache.get(filePath); if (cached !== undefined && cached === readFileSync(filePath, 'utf8')) { debugLog(workspace.name, `Watcher: = ${relativePath}`); continue; } modified.add(filePath); debugLog(workspace.name, `Watcher: ± ${relativePath}`); break; } } principal.invalidateFile(filePath); } if (added.size === 0 && deleted.size === 0 && modified.size === 0) return; clearResolverCache(); invalidateCache(graph); unreferencedFiles.clear(); const cachedUnusedFiles = collector.purge(); for (const filePath of added) cachedUnusedFiles.add(filePath); for (const filePath of deleted) cachedUnusedFiles.delete(filePath); const filePaths = principal.getUsedResolvedFiles(); if (added.size > 0 || deleted.size > 0) { graph.clear(); for (const filePath of filePaths) { const workspace = chief.findWorkspaceByFilePath(filePath); if (workspace) { analyzeSourceFile(filePath, principal); } } } else { for (const [filePath, file] of graph) { if (filePaths.includes(filePath)) { file.importedBy = undefined; } else { graph.delete(filePath); analyzedFiles.delete(filePath); const workspace = chief.findWorkspaceByFilePath(filePath); if (workspace && principal.projectPaths.has(filePath)) cachedUnusedFiles.add(filePath); } } for (const filePath of filePaths) { if (!graph.has(filePath)) { const workspace = chief.findWorkspaceByFilePath(filePath); if (workspace) { analyzeSourceFile(filePath, principal); } } } for (const filePath of modified) { if (!cachedUnusedFiles.has(filePath)) { const workspace = chief.findWorkspaceByFilePath(filePath); if (workspace) { if (principal.projectPaths.has(filePath) || graph.has(filePath)) { analyzeSourceFile(filePath, principal); } } } } for (const filePath of filePaths) { const file = graph.get(filePath); if (file?.internalImportCache) updateImportMap(file, file.internalImportCache, graph); } } await analyze(); const unusedFiles = [...cachedUnusedFiles].filter(filePath => !analyzedFiles.has(filePath)); collector.addFilesIssues(unusedFiles); collector.addFileCounts({ processed: analyzedFiles.size, unused: unusedFiles.length }); for (const issue of collector.getRetainedIssues()) collector.addIssue(issue); const update = createUpdate({ startTime }); if (onFileChange) onFileChange(Object.assign({ issues: getIssues().issues }, update)); return update; }; const listener = (eventType, filePath) => { debugLog('*', `(raw) ${eventType} ${filePath}`); if (typeof filePath === 'string') { const normalizedPath = toPosix(filePath); const type = eventType === 'rename' ? (isFile(options.cwd, normalizedPath) ? 'added' : 'deleted') : 'modified'; handleFileChanges([{ type, filePath: normalizedPath }]); } }; const getIssues = () => collector.getIssues(); const getEntryPaths = () => entryPaths; const getGraph = () => graph; if (onFileChange) onFileChange({ issues: getIssues().issues }); return { listener, handleFileChanges, getEntryPaths, getGraph, getIssues }; };