knip
Version:
Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects
138 lines (137 loc) • 6.32 kB
JavaScript
import { invalidateCache } from '../graph-explorer/cache.js';
import { debugLog } from './debug.js';
import { isFile } from './fs.js';
import { updateImportMap } from './module-graph.js';
import { join, toAbsolute, toRelative } from './path.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, factory, 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;
const principal = factory.getPrincipalByPackageName(workspace.pkgName);
if (!principal)
continue;
switch (change.type) {
case 'added':
added.add(filePath);
principal.addProjectPath(filePath);
principal.deletedFiles.delete(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:
modified.add(filePath);
debugLog(workspace.name, `Watcher: ± ${relativePath}`);
break;
}
principal.invalidateFile(filePath);
}
if (added.size === 0 && deleted.size === 0 && modified.size === 0)
return createUpdate({ startTime });
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 = factory.getPrincipals().flatMap(p => p.getUsedResolvedFiles());
if (added.size > 0 || deleted.size > 0) {
graph.clear();
for (const filePath of filePaths) {
const workspace = chief.findWorkspaceByFilePath(filePath);
if (workspace) {
const principal = factory.getPrincipalByPackageName(workspace.pkgName);
if (principal)
analyzeSourceFile(filePath, principal);
}
}
}
else {
for (const [filePath, file] of graph) {
if (filePaths.includes(filePath)) {
file.imported = undefined;
}
else {
graph.delete(filePath);
analyzedFiles.delete(filePath);
const workspace = chief.findWorkspaceByFilePath(filePath);
if (workspace) {
const principal = factory.getPrincipalByPackageName(workspace.pkgName);
if (principal?.projectPaths.has(filePath))
cachedUnusedFiles.add(filePath);
}
}
}
for (const filePath of filePaths) {
if (!graph.has(filePath)) {
const workspace = chief.findWorkspaceByFilePath(filePath);
if (workspace) {
const principal = factory.getPrincipalByPackageName(workspace.pkgName);
if (principal)
analyzeSourceFile(filePath, principal);
}
}
}
for (const filePath of modified) {
if (!cachedUnusedFiles.has(filePath)) {
const workspace = chief.findWorkspaceByFilePath(filePath);
if (workspace) {
const principal = factory.getPrincipalByPackageName(workspace.pkgName);
if (principal)
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 type = eventType === 'rename' ? (isFile(join(options.cwd, filePath)) ? 'added' : 'deleted') : 'modified';
handleFileChanges([{ type, filePath }]);
}
};
const getIssues = () => collector.getIssues();
const getEntryPaths = () => entryPaths;
const getGraph = () => graph;
if (onFileChange)
onFileChange({ issues: getIssues().issues });
return { listener, handleFileChanges, getEntryPaths, getGraph, getIssues };
};