UNPKG

knip

Version:

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

101 lines (100 loc) 4.84 kB
import { relative, toRelative } from '../../util/path.js'; import { Table } from '../../util/table.js'; import { byPathDepth } from '../../util/workspace.js'; import { bright, dim, getColoredTitle, getDimmedTitle } from './util.js'; const getWorkspaceName = (hint) => hint.workspaceName && hint.workspaceName !== '.' && hint.type !== 'workspace-unconfigured' && hint.type !== 'package-entry' ? hint.workspaceName : ''; const getTableForHints = (hints) => { const table = new Table({ truncateStart: ['identifier', 'workspace', 'filePath'] }); for (const hint of hints) { table.row(); table.cell('identifier', hint.identifier.toString()); table.cell('workspace', getWorkspaceName(hint)); table.cell('filePath', hint.filePath ? relative(hint.filePath) : ''); table.cell('description', dim(hint.message)); } return table; }; const type = (id) => bright(id.split('-').at(0)); const unused = (options) => `Remove from ${type(options.type)}`; const empty = (options) => `Refine ${type(options.type)} pattern (no matches)`; const remove = (options) => `Remove redundant ${type(options.type)} pattern`; const topLevel = (options) => `Remove, or move unused top-level ${type(options.type)} to one of ${bright('"workspaces"')}`; const add = (options) => options.configFilePath ? `Add ${bright('entry')} and/or refine ${bright('project')} files in ${bright(`workspaces["${options.workspaceName}"]`)} (${options.size} unused files)` : `Create ${bright('knip.json')} configuration file with ${bright(`workspaces["${options.workspaceName}"]`)} object (${options.size} unused files)`; const packageEntry = () => 'Package entry file not found'; const hintPrinters = new Map([ ['ignoreBinaries', { print: unused }], ['ignoreDependencies', { print: unused }], ['ignoreUnresolved', { print: unused }], ['ignoreWorkspaces', { print: unused }], ['entry-empty', { print: empty }], ['project-empty', { print: empty }], ['entry-redundant', { print: remove }], ['project-redundant', { print: remove }], ['workspace-unconfigured', { print: add }], ['entry-top-level', { print: topLevel }], ['project-top-level', { print: topLevel }], ['package-entry', { print: packageEntry }], ]); const hintTypesOrder = [ ['workspace-unconfigured'], ['entry-top-level', 'project-top-level'], ['ignoreWorkspaces'], ['ignoreDependencies'], ['ignoreBinaries'], ['ignoreUnresolved'], ['entry-empty', 'project-empty', 'entry-redundant', 'project-redundant'], ['package-entry'], ]; export const printConfigurationHints = ({ counters, issues, tagHints, configurationHints, isTreatConfigHintsAsErrors, includedWorkspaces, configFilePath, }) => { if (counters.files > 20) { const workspaces = includedWorkspaces .map(workspace => workspace.dir) .sort(byPathDepth) .reverse() .map(dir => ({ dir, size: 0 })); for (const filePath of issues.files) { const workspace = workspaces.find(ws => filePath.startsWith(ws.dir)); if (workspace) workspace.size++; } const hlWorkspaces = workspaces.sort((a, b) => b.size - a.size).filter(ws => ws.size > 1); for (const { dir, size } of hlWorkspaces) { const identifier = toRelative(dir) || '.'; configurationHints.add({ type: 'workspace-unconfigured', workspaceName: identifier, identifier, size }); } } if (configurationHints.size > 0) { const getTitle = isTreatConfigHintsAsErrors ? getColoredTitle : getDimmedTitle; console.log(getTitle('Configuration hints', configurationHints.size)); const hintsByType = new Map(); for (const hint of configurationHints) { const hints = hintsByType.get(hint.type) ?? []; hintsByType.set(hint.type, [...hints, hint]); } const rows = hintTypesOrder.flatMap(hintTypes => hintTypes.flatMap(hintType => { const hints = hintsByType.get(hintType) ?? []; return hints.map(hint => { hint.filePath ??= configFilePath; const hintPrinter = hintPrinters.get(hint.type); const message = hintPrinter ? hintPrinter.print({ ...hint, configFilePath }) : ''; return { ...hint, message }; }); })); console.warn(getTableForHints(rows).toString()); } if (tagHints.size > 0) { console.log(getColoredTitle('Tag issues', tagHints.size)); for (const hint of tagHints) { const { filePath, identifier, tagName } = hint; const message = `Unused tag in ${toRelative(filePath)}:`; console.warn(dim(message), `${identifier}${tagName}`); } } };