@@ -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`;
0 commit comments