Skip to content

Commit 0ef1a87

Browse files
feat: Enhance Knip workflow for unused code analysis with detailed issue reporting
## Summary This update improves the Knip GitHub Action by adding detailed reporting of unused code issues. The changes include: - Enhanced issue counting to include a breakdown of items per category. - Added a detailed summary section in the PR comment that lists added and removed items for each category when there are changes. - Improved error handling to ensure consistent return values even when parsing fails. These enhancements provide clearer insights into unused code changes between branches, aiding in code maintenance and quality.
1 parent 47e1f56 commit 0ef1a87

File tree

2 files changed

+60
-19
lines changed

2 files changed

+60
-19
lines changed

.github/workflows/knip.yml

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,29 +47,44 @@ jobs:
4747
script: |
4848
const fs = require('fs');
4949
50+
const issueCategories = [
51+
'files', 'dependencies', 'devDependencies', 'unlisted',
52+
'unresolved', 'binaries', 'exports', 'types',
53+
'enumMembers', 'duplicates'
54+
];
55+
5056
function countIssues(filePath) {
5157
try {
5258
const raw = fs.readFileSync(filePath, 'utf8');
5359
const data = JSON.parse(raw);
5460
const counts = {};
55-
const issueCategories = [
56-
'files', 'dependencies', 'devDependencies', 'unlisted',
57-
'unresolved', 'binaries', 'exports', 'types',
58-
'enumMembers', 'duplicates'
59-
];
61+
// items maps category -> Set of "file:name" identifiers
62+
const items = {};
63+
for (const cat of issueCategories) items[cat] = new Set();
64+
// Top-level "files" are unused files (just strings)
65+
for (const f of data.files || []) {
66+
counts.files = (counts.files || 0) + 1;
67+
items.files.add(f);
68+
}
6069
for (const issue of data.issues || []) {
6170
for (const cat of issueCategories) {
71+
if (cat === 'files') continue;
6272
if (Array.isArray(issue[cat]) && issue[cat].length > 0) {
6373
counts[cat] = (counts[cat] || 0) + issue[cat].length;
74+
for (const item of issue[cat]) {
75+
items[cat].add(`${issue.file}:${item.name}`);
76+
}
6477
}
6578
}
6679
}
6780
let total = 0;
6881
for (const v of Object.values(counts)) total += v;
69-
return { counts, total };
82+
return { counts, total, items };
7083
} catch (e) {
7184
console.log(`Failed to parse ${filePath}: ${e.message}`);
72-
return { counts: {}, total: 0 };
85+
const items = {};
86+
for (const cat of issueCategories) items[cat] = new Set();
87+
return { counts: {}, total: 0, items };
7388
}
7489
}
7590
@@ -94,18 +109,40 @@ jobs:
94109
const sign = diff > 0 ? '+' : '';
95110
96111
let body = `## Knip - Unused Code Analysis\n\n`;
97-
body += `${emoji} **${sign}${diff}** change in total issues (${main.total} on main → ${pr.total} on PR)\n\n`;
98-
body += `| Category | main | PR | Diff |\n`;
99-
body += `|----------|-----:|---:|-----:|\n`;
100-
101-
for (const [key, label] of categories) {
102-
const mainCount = main.counts[key] || 0;
103-
const prCount = pr.counts[key] || 0;
104-
const catDiff = prCount - mainCount;
105-
if (mainCount === 0 && prCount === 0) continue;
106-
const catSign = catDiff > 0 ? '+' : '';
107-
const catEmoji = catDiff > 0 ? ' 🔴' : catDiff < 0 ? ' 🟢' : '';
108-
body += `| ${label} | ${mainCount} | ${prCount} | ${catSign}${catDiff}${catEmoji} |\n`;
112+
113+
if (diff === 0) {
114+
body += `${emoji} No changes detected (${main.total} issues on both main and PR)\n`;
115+
} else {
116+
body += `${emoji} **${sign}${diff}** change in total issues (${main.total} on main → ${pr.total} on PR)\n\n`;
117+
body += `| Category | main | PR | Diff |\n`;
118+
body += `|----------|-----:|---:|-----:|\n`;
119+
120+
const detailLines = [];
121+
for (const [key, label] of categories) {
122+
const mainCount = main.counts[key] || 0;
123+
const prCount = pr.counts[key] || 0;
124+
const catDiff = prCount - mainCount;
125+
if (mainCount === 0 && prCount === 0) continue;
126+
const catSign = catDiff > 0 ? '+' : '';
127+
const catEmoji = catDiff > 0 ? ' 🔴' : catDiff < 0 ? ' 🟢' : '';
128+
body += `| ${label} | ${mainCount} | ${prCount} | ${catSign}${catDiff}${catEmoji} |\n`;
129+
130+
if (catDiff !== 0) {
131+
const added = [...pr.items[key]].filter(x => !main.items[key].has(x));
132+
const removed = [...main.items[key]].filter(x => !pr.items[key].has(x));
133+
if (added.length || removed.length) {
134+
detailLines.push(`**${label}**`);
135+
for (const a of added) detailLines.push(`- 🔴 \`${a}\``);
136+
for (const r of removed) detailLines.push(`- 🟢 ~\`${r}\`~`);
137+
}
138+
}
139+
}
140+
141+
if (detailLines.length) {
142+
body += `\n<details><summary>Details</summary>\n\n`;
143+
body += detailLines.join('\n') + '\n';
144+
body += `</details>\n`;
145+
}
109146
}
110147
111148
body += `\n<details><summary>What is this?</summary>\n\n`;

packages/app/src/components/Alerts.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ type Webhook = {
1818
name: string;
1919
};
2020

21+
export function UnusedComponent() {
22+
return <div>Unused component</div>;
23+
}
24+
2125
const WebhookChannelForm = <T extends object>(
2226
props: Partial<SelectProps<T>>,
2327
) => {

0 commit comments

Comments
 (0)