pi-lens 2.0.7 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/clients/ast-grep-client.test.ts +146 -116
- package/clients/ast-grep-client.ts +645 -551
- package/clients/biome-client.test.ts +154 -137
- package/clients/biome-client.ts +397 -337
- package/clients/complexity-client.test.ts +188 -200
- package/clients/complexity-client.ts +815 -667
- package/clients/dependency-checker.test.ts +55 -55
- package/clients/dependency-checker.ts +358 -333
- package/clients/go-client.test.ts +121 -111
- package/clients/go-client.ts +218 -216
- package/clients/jscpd-client.test.ts +132 -132
- package/clients/jscpd-client.ts +155 -118
- package/clients/knip-client.test.ts +123 -133
- package/clients/knip-client.ts +231 -218
- package/clients/metrics-client.test.ts +171 -167
- package/clients/metrics-client.ts +283 -252
- package/clients/ruff-client.test.ts +128 -117
- package/clients/ruff-client.ts +300 -269
- package/clients/rust-client.test.ts +104 -85
- package/clients/rust-client.ts +241 -234
- package/clients/subprocess-client.ts +1 -1
- package/clients/test-runner-client.test.ts +248 -215
- package/clients/test-runner-client.ts +728 -608
- package/clients/test-utils.ts +10 -3
- package/clients/todo-scanner.test.ts +288 -202
- package/clients/todo-scanner.ts +225 -187
- package/clients/type-coverage-client.test.ts +119 -119
- package/clients/type-coverage-client.ts +142 -115
- package/clients/types.ts +28 -28
- package/clients/typescript-client.test.ts +99 -93
- package/clients/typescript-client.ts +527 -502
- package/index.ts +662 -212
- package/package.json +1 -1
- package/tsconfig.json +12 -12
package/clients/knip-client.ts
CHANGED
|
@@ -15,230 +15,243 @@ import * as path from "node:path";
|
|
|
15
15
|
// --- Types ---
|
|
16
16
|
|
|
17
17
|
export interface KnipIssue {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
type: "export" | "file" | "dependency" | "devDependency" | "unlisted" | "bin";
|
|
19
|
+
name: string;
|
|
20
|
+
file?: string;
|
|
21
|
+
line?: number;
|
|
22
|
+
package?: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface KnipResult {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
success: boolean;
|
|
27
|
+
issues: KnipIssue[];
|
|
28
|
+
unusedExports: KnipIssue[];
|
|
29
|
+
unusedFiles: KnipIssue[];
|
|
30
|
+
unusedDeps: KnipIssue[];
|
|
31
|
+
unlistedDeps: KnipIssue[];
|
|
32
|
+
summary: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// --- Client ---
|
|
36
36
|
|
|
37
37
|
export class KnipClient {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
38
|
+
private knipAvailable: boolean | null = null;
|
|
39
|
+
private log: (msg: string) => void;
|
|
40
|
+
|
|
41
|
+
constructor(verbose = false) {
|
|
42
|
+
this.log = verbose
|
|
43
|
+
? (msg: string) => console.log(`[knip] ${msg}`)
|
|
44
|
+
: () => {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if knip CLI is available
|
|
49
|
+
*/
|
|
50
|
+
isAvailable(): boolean {
|
|
51
|
+
if (this.knipAvailable !== null) return this.knipAvailable;
|
|
52
|
+
|
|
53
|
+
const result = spawnSync("npx", ["knip", "--version"], {
|
|
54
|
+
encoding: "utf-8",
|
|
55
|
+
timeout: 10000,
|
|
56
|
+
shell: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
this.knipAvailable = !result.error && result.status === 0;
|
|
60
|
+
if (this.knipAvailable) {
|
|
61
|
+
this.log(`Knip available`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return this.knipAvailable;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Run knip analysis on the project
|
|
69
|
+
*/
|
|
70
|
+
analyze(cwd?: string): KnipResult {
|
|
71
|
+
if (!this.isAvailable()) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
issues: [],
|
|
75
|
+
unusedExports: [],
|
|
76
|
+
unusedFiles: [],
|
|
77
|
+
unusedDeps: [],
|
|
78
|
+
unlistedDeps: [],
|
|
79
|
+
summary: "Knip not available. Install with: npm install -D knip",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const targetDir = cwd || process.cwd();
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const result = spawnSync(
|
|
87
|
+
"npx",
|
|
88
|
+
[
|
|
89
|
+
"knip",
|
|
90
|
+
"--reporter=json",
|
|
91
|
+
"--include",
|
|
92
|
+
"exports,types,dependencies,unlisted",
|
|
93
|
+
],
|
|
94
|
+
{
|
|
95
|
+
encoding: "utf-8",
|
|
96
|
+
timeout: 30000,
|
|
97
|
+
cwd: targetDir,
|
|
98
|
+
shell: true,
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Knip exits 0 on success (even with issues), 1 on errors
|
|
103
|
+
const output = result.stdout || "";
|
|
104
|
+
if (!output.trim()) {
|
|
105
|
+
return {
|
|
106
|
+
success: true,
|
|
107
|
+
issues: [],
|
|
108
|
+
unusedExports: [],
|
|
109
|
+
unusedFiles: [],
|
|
110
|
+
unusedDeps: [],
|
|
111
|
+
unlistedDeps: [],
|
|
112
|
+
summary: "No issues found",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return this.parseOutput(output);
|
|
117
|
+
} catch (err: any) {
|
|
118
|
+
this.log(`Analysis error: ${err.message}`);
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
issues: [],
|
|
122
|
+
unusedExports: [],
|
|
123
|
+
unusedFiles: [],
|
|
124
|
+
unusedDeps: [],
|
|
125
|
+
unlistedDeps: [],
|
|
126
|
+
summary: `Error: ${err.message}`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Find unused exports in a specific file
|
|
133
|
+
*/
|
|
134
|
+
findUnusedExports(filePath: string): string[] {
|
|
135
|
+
const result = this.analyze(path.dirname(filePath));
|
|
136
|
+
const basename = path.basename(filePath);
|
|
137
|
+
|
|
138
|
+
return result.unusedExports
|
|
139
|
+
.filter((e) => e.file?.includes(basename))
|
|
140
|
+
.map((e) => e.name);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Format results for LLM consumption
|
|
145
|
+
*/
|
|
146
|
+
formatResult(result: KnipResult, maxItems = 20): string {
|
|
147
|
+
if (!result.success) return `[Knip] ${result.summary}`;
|
|
148
|
+
if (result.issues.length === 0) return "";
|
|
149
|
+
|
|
150
|
+
let output = `[Knip] ${result.issues.length} issue(s)`;
|
|
151
|
+
if (result.unusedExports.length)
|
|
152
|
+
output += ` — ${result.unusedExports.length} unused export(s)`;
|
|
153
|
+
if (result.unusedFiles.length)
|
|
154
|
+
output += ` — ${result.unusedFiles.length} unused file(s)`;
|
|
155
|
+
if (result.unusedDeps.length)
|
|
156
|
+
output += ` — ${result.unusedDeps.length} unused dep(s)`;
|
|
157
|
+
if (result.unlistedDeps.length)
|
|
158
|
+
output += ` — ${result.unlistedDeps.length} unlisted dep(s)`;
|
|
159
|
+
output += ":\n";
|
|
160
|
+
|
|
161
|
+
// Show unused exports first (most useful for refactoring)
|
|
162
|
+
if (result.unusedExports.length > 0) {
|
|
163
|
+
output += "\n Unused exports:\n";
|
|
164
|
+
for (const issue of result.unusedExports.slice(0, maxItems)) {
|
|
165
|
+
const loc = issue.file ? ` (${path.basename(issue.file)})` : "";
|
|
166
|
+
output += ` - ${issue.name}${loc}\n`;
|
|
167
|
+
}
|
|
168
|
+
if (result.unusedExports.length > maxItems) {
|
|
169
|
+
output += ` ... and ${result.unusedExports.length - maxItems} more\n`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Show unused files
|
|
174
|
+
if (result.unusedFiles.length > 0) {
|
|
175
|
+
output += "\n Unused files:\n";
|
|
176
|
+
for (const issue of result.unusedFiles.slice(0, 10)) {
|
|
177
|
+
output += ` - ${issue.name}\n`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Show unused deps (might be worth removing)
|
|
182
|
+
if (result.unusedDeps.length > 0) {
|
|
183
|
+
output += "\n Unused dependencies:\n";
|
|
184
|
+
for (const issue of result.unusedDeps) {
|
|
185
|
+
output += ` - ${issue.package || issue.name}\n`;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return output;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// --- Internal ---
|
|
193
|
+
|
|
194
|
+
private parseOutput(output: string): KnipResult {
|
|
195
|
+
try {
|
|
196
|
+
const data = JSON.parse(output);
|
|
197
|
+
const issues: KnipIssue[] = [];
|
|
198
|
+
const unusedExports: KnipIssue[] = [];
|
|
199
|
+
const unusedFiles: KnipIssue[] = [];
|
|
200
|
+
const unusedDeps: KnipIssue[] = [];
|
|
201
|
+
const unlistedDeps: KnipIssue[] = [];
|
|
202
|
+
|
|
203
|
+
// Knip JSON format: { issues: [ { file, exports:[], files:[], dependencies:[], ... } ] }
|
|
204
|
+
const fileEntries: any[] = data.issues ?? [];
|
|
205
|
+
|
|
206
|
+
for (const entry of fileEntries) {
|
|
207
|
+
const file: string = entry.file ?? "";
|
|
208
|
+
|
|
209
|
+
const push = (
|
|
210
|
+
arr: any[],
|
|
211
|
+
type: KnipIssue["type"],
|
|
212
|
+
target: KnipIssue[],
|
|
213
|
+
) => {
|
|
214
|
+
for (const item of arr) {
|
|
215
|
+
const issue: KnipIssue = {
|
|
216
|
+
type,
|
|
217
|
+
name: item.name ?? item.symbol ?? String(item),
|
|
218
|
+
file,
|
|
219
|
+
line: item.line,
|
|
220
|
+
package: item.package,
|
|
221
|
+
};
|
|
222
|
+
issues.push(issue);
|
|
223
|
+
target.push(issue);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
push(entry.exports ?? [], "export", unusedExports);
|
|
228
|
+
push(entry.types ?? [], "export", unusedExports);
|
|
229
|
+
push(entry.files ?? [], "file", unusedFiles);
|
|
230
|
+
push(entry.dependencies ?? [], "dependency", unusedDeps);
|
|
231
|
+
push(entry.devDependencies ?? [], "devDependency", unusedDeps);
|
|
232
|
+
push(entry.unlisted ?? [], "unlisted", unlistedDeps);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
success: true,
|
|
237
|
+
issues,
|
|
238
|
+
unusedExports,
|
|
239
|
+
unusedFiles,
|
|
240
|
+
unusedDeps,
|
|
241
|
+
unlistedDeps,
|
|
242
|
+
summary: `Found ${issues.length} issues`,
|
|
243
|
+
};
|
|
244
|
+
} catch {
|
|
245
|
+
this.log("Failed to parse knip JSON output");
|
|
246
|
+
return {
|
|
247
|
+
success: false,
|
|
248
|
+
issues: [],
|
|
249
|
+
unusedExports: [],
|
|
250
|
+
unusedFiles: [],
|
|
251
|
+
unusedDeps: [],
|
|
252
|
+
unlistedDeps: [],
|
|
253
|
+
summary: "Failed to parse output",
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
244
257
|
}
|