knip
Version:
Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects
101 lines (100 loc) • 4.84 kB
JavaScript
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}`);
}
}
};