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.
Files changed (34) hide show
  1. package/clients/ast-grep-client.test.ts +146 -116
  2. package/clients/ast-grep-client.ts +645 -551
  3. package/clients/biome-client.test.ts +154 -137
  4. package/clients/biome-client.ts +397 -337
  5. package/clients/complexity-client.test.ts +188 -200
  6. package/clients/complexity-client.ts +815 -667
  7. package/clients/dependency-checker.test.ts +55 -55
  8. package/clients/dependency-checker.ts +358 -333
  9. package/clients/go-client.test.ts +121 -111
  10. package/clients/go-client.ts +218 -216
  11. package/clients/jscpd-client.test.ts +132 -132
  12. package/clients/jscpd-client.ts +155 -118
  13. package/clients/knip-client.test.ts +123 -133
  14. package/clients/knip-client.ts +231 -218
  15. package/clients/metrics-client.test.ts +171 -167
  16. package/clients/metrics-client.ts +283 -252
  17. package/clients/ruff-client.test.ts +128 -117
  18. package/clients/ruff-client.ts +300 -269
  19. package/clients/rust-client.test.ts +104 -85
  20. package/clients/rust-client.ts +241 -234
  21. package/clients/subprocess-client.ts +1 -1
  22. package/clients/test-runner-client.test.ts +248 -215
  23. package/clients/test-runner-client.ts +728 -608
  24. package/clients/test-utils.ts +10 -3
  25. package/clients/todo-scanner.test.ts +288 -202
  26. package/clients/todo-scanner.ts +225 -187
  27. package/clients/type-coverage-client.test.ts +119 -119
  28. package/clients/type-coverage-client.ts +142 -115
  29. package/clients/types.ts +28 -28
  30. package/clients/typescript-client.test.ts +99 -93
  31. package/clients/typescript-client.ts +527 -502
  32. package/index.ts +662 -212
  33. package/package.json +1 -1
  34. package/tsconfig.json +12 -12
@@ -8,259 +8,266 @@
8
8
  */
9
9
 
10
10
  import { spawnSync } from "node:child_process";
11
- import * as path from "node:path";
12
11
  import * as fs from "node:fs";
12
+ import * as path from "node:path";
13
13
 
14
14
  // --- Types ---
15
15
 
16
16
  export interface RustDiagnostic {
17
- line: number;
18
- column: number;
19
- endLine: number;
20
- endColumn: number;
21
- severity: "error" | "warning" | "note" | "help";
22
- message: string;
23
- code?: string;
24
- file: string;
17
+ line: number;
18
+ column: number;
19
+ endLine: number;
20
+ endColumn: number;
21
+ severity: "error" | "warning" | "note" | "help";
22
+ message: string;
23
+ code?: string;
24
+ file: string;
25
25
  }
26
26
 
27
27
  interface CargoMessage {
28
- reason: "compiler-artifact" | "compiler-message" | "build-script-executed";
29
- message?: {
30
- level: string;
31
- code?: string;
32
- message: string;
33
- spans?: Array<{
34
- line_start: number;
35
- line_end: number;
36
- column_start: number;
37
- column_end: number;
38
- file_name: string;
39
- }>;
40
- };
28
+ reason: "compiler-artifact" | "compiler-message" | "build-script-executed";
29
+ message?: {
30
+ level: string;
31
+ code?: string;
32
+ message: string;
33
+ spans?: Array<{
34
+ line_start: number;
35
+ line_end: number;
36
+ column_start: number;
37
+ column_end: number;
38
+ file_name: string;
39
+ }>;
40
+ };
41
41
  }
42
42
 
43
43
  // --- Common install paths ---
44
44
 
45
45
  const CARGO_WINDOWS_PATHS = [
46
- path.join(process.env.USERPROFILE || "", ".cargo", "bin", "cargo.exe"),
47
- "C:\\cargo\\bin\\cargo.exe",
48
- "cargo.exe", // PATH
46
+ path.join(process.env.USERPROFILE || "", ".cargo", "bin", "cargo.exe"),
47
+ "C:\\cargo\\bin\\cargo.exe",
48
+ "cargo.exe", // PATH
49
49
  ];
50
50
 
51
51
  const CARGO_UNIX_PATHS = [
52
- path.join(process.env.HOME || "", ".cargo", "bin", "cargo"),
53
- "/usr/local/cargo/bin/cargo",
54
- "/usr/bin/cargo",
55
- "cargo", // PATH
52
+ path.join(process.env.HOME || "", ".cargo", "bin", "cargo"),
53
+ "/usr/local/cargo/bin/cargo",
54
+ "/usr/bin/cargo",
55
+ "cargo", // PATH
56
56
  ];
57
57
 
58
58
  // --- Client ---
59
59
 
60
60
  export class RustClient {
61
- private cargoAvailable: boolean | null = null;
62
- private cargoPath: string | null = null;
63
- private log: (msg: string) => void;
64
-
65
- constructor(verbose = false) {
66
- this.log = verbose
67
- ? (msg: string) => console.log(`[rust] ${msg}`)
68
- : () => {};
69
- }
70
-
71
- /**
72
- * Find cargo executable path
73
- */
74
- private findCargoPath(): string | null {
75
- if (this.cargoPath) return this.cargoPath;
76
-
77
- const paths = process.platform === "win32" ? CARGO_WINDOWS_PATHS : CARGO_UNIX_PATHS;
78
-
79
- for (const p of paths) {
80
- try {
81
- if (p.includes("\\") || p.includes("/")) {
82
- if (fs.existsSync(p)) {
83
- this.cargoPath = p;
84
- return p;
85
- }
86
- } else {
87
- const result = spawnSync(p, ["--version"], {
88
- encoding: "utf-8",
89
- timeout: 3000,
90
- shell: true,
91
- });
92
- if (!result.error && result.status === 0) {
93
- this.cargoPath = p;
94
- return p;
95
- }
96
- }
97
- } catch {}
98
- }
99
-
100
- return null;
101
- }
102
-
103
- /**
104
- * Check if cargo is installed
105
- */
106
- isAvailable(): boolean {
107
- if (this.cargoAvailable !== null) return this.cargoAvailable;
108
- this.cargoAvailable = this.findCargoPath() !== null;
109
- if (this.cargoAvailable) {
110
- this.log(`Cargo found: ${this.cargoPath}`);
111
- }
112
- return this.cargoAvailable;
113
- }
114
-
115
- /**
116
- * Check if a file is a Rust file
117
- */
118
- isRustFile(filePath: string): boolean {
119
- return path.extname(filePath).toLowerCase() === ".rs";
120
- }
121
-
122
- /**
123
- * Run cargo check on the project
124
- */
125
- checkFile(filePath: string, cwd: string): RustDiagnostic[] {
126
- const cargoExe = this.findCargoPath();
127
- if (!cargoExe) return [];
128
-
129
- const absolutePath = path.resolve(filePath);
130
- if (!fs.existsSync(absolutePath)) return [];
131
-
132
- try {
133
- const cargoCmd = cargoExe.includes(" ") ? `"${cargoExe}"` : cargoExe;
134
- const result = spawnSync(cargoCmd, [
135
- "check",
136
- "--message-format", "json",
137
- ], {
138
- encoding: "utf-8",
139
- timeout: 60000,
140
- cwd,
141
- shell: true,
142
- });
143
-
144
- const output = result.stdout || "";
145
- return this.parseJsonOutput(output, absolutePath);
146
- } catch (err: any) {
147
- this.log(`Check error: ${err.message}`);
148
- return [];
149
- }
150
- }
151
-
152
- /**
153
- * Run clippy for additional lints
154
- */
155
- clippyCheck(cwd: string): RustDiagnostic[] {
156
- if (!this.isAvailable()) return [];
157
-
158
- try {
159
- const result = spawnSync("cargo", [
160
- "clippy",
161
- "--message-format", "json",
162
- ], {
163
- encoding: "utf-8",
164
- timeout: 60000,
165
- cwd,
166
- shell: true,
167
- });
168
-
169
- const output = result.stdout || "";
170
- return this.parseJsonOutput(output, "");
171
- } catch (err: any) {
172
- this.log(`Clippy error: ${err.message}`);
173
- return [];
174
- }
175
- }
176
-
177
- /**
178
- * Format diagnostics for LLM consumption
179
- */
180
- formatDiagnostics(diags: RustDiagnostic[], maxItems = 10): string {
181
- if (diags.length === 0) return "";
182
-
183
- const errors = diags.filter((d) => d.severity === "error");
184
- const warnings = diags.filter((d) => d.severity === "warning");
185
-
186
- let output = `[Rust] ${diags.length} issue(s)`;
187
- if (errors.length) output += ` — ${errors.length} error(s)`;
188
- if (warnings.length) output += ` — ${warnings.length} warning(s)`;
189
- output += ":\n";
190
-
191
- for (const d of diags.slice(0, maxItems)) {
192
- const loc = `L${d.line}:${d.column}`;
193
- const code = d.code ? ` [${d.code}]` : "";
194
- output += ` [${d.severity}] ${loc} ${d.message.slice(0, 200)}${code}\n`;
195
- }
196
-
197
- if (diags.length > maxItems) {
198
- output += ` ... and ${diags.length - maxItems} more\n`;
199
- }
200
-
201
- return output;
202
- }
203
-
204
- // --- Internal ---
205
-
206
- private parseJsonOutput(output: string, filterFile: string): RustDiagnostic[] {
207
- if (!output.trim()) return [];
208
-
209
- const diags: RustDiagnostic[] = [];
210
- const lines = output.split("\n").filter((l) => l.trim());
211
-
212
- for (const line of lines) {
213
- try {
214
- const msg: CargoMessage = JSON.parse(line);
215
-
216
- if (msg.reason === "compiler-message" && msg.message) {
217
- const { level, message, spans, code } = msg.message;
218
-
219
- // Only include errors and warnings
220
- if (level !== "error" && level !== "warning" && level !== "note") {
221
- continue;
222
- }
223
-
224
- // Get location from spans
225
- if (spans && spans.length > 0) {
226
- for (const span of spans) {
227
- const file = span.file_name;
228
-
229
- // Filter to specific file if provided
230
- if (filterFile && path.resolve(file) !== path.resolve(filterFile)) {
231
- continue;
232
- }
233
-
234
- diags.push({
235
- line: span.line_start,
236
- column: span.column_start - 1,
237
- endLine: span.line_end,
238
- endColumn: span.column_end - 1,
239
- severity: level as RustDiagnostic["severity"],
240
- message: message.slice(0, 300),
241
- code,
242
- file: path.resolve(file),
243
- });
244
- }
245
- } else {
246
- // No span info, add as general diagnostic
247
- diags.push({
248
- line: 1,
249
- column: 0,
250
- endLine: 1,
251
- endColumn: 0,
252
- severity: level as RustDiagnostic["severity"],
253
- message: message.slice(0, 300),
254
- code,
255
- file: filterFile || "",
256
- });
257
- }
258
- }
259
- } catch {
260
- // Skip non-JSON lines
261
- }
262
- }
263
-
264
- return diags;
265
- }
61
+ private cargoAvailable: boolean | null = null;
62
+ private cargoPath: string | null = null;
63
+ private log: (msg: string) => void;
64
+
65
+ constructor(verbose = false) {
66
+ this.log = verbose
67
+ ? (msg: string) => console.log(`[rust] ${msg}`)
68
+ : () => {};
69
+ }
70
+
71
+ /**
72
+ * Find cargo executable path
73
+ */
74
+ private findCargoPath(): string | null {
75
+ if (this.cargoPath) return this.cargoPath;
76
+
77
+ const paths =
78
+ process.platform === "win32" ? CARGO_WINDOWS_PATHS : CARGO_UNIX_PATHS;
79
+
80
+ for (const p of paths) {
81
+ try {
82
+ if (p.includes("\\") || p.includes("/")) {
83
+ if (fs.existsSync(p)) {
84
+ this.cargoPath = p;
85
+ return p;
86
+ }
87
+ } else {
88
+ const result = spawnSync(p, ["--version"], {
89
+ encoding: "utf-8",
90
+ timeout: 3000,
91
+ shell: true,
92
+ });
93
+ if (!result.error && result.status === 0) {
94
+ this.cargoPath = p;
95
+ return p;
96
+ }
97
+ }
98
+ } catch (err) { void err; }
99
+ }
100
+
101
+ return null;
102
+ }
103
+
104
+ /**
105
+ * Check if cargo is installed
106
+ */
107
+ isAvailable(): boolean {
108
+ if (this.cargoAvailable !== null) return this.cargoAvailable;
109
+ this.cargoAvailable = this.findCargoPath() !== null;
110
+ if (this.cargoAvailable) {
111
+ this.log(`Cargo found: ${this.cargoPath}`);
112
+ }
113
+ return this.cargoAvailable;
114
+ }
115
+
116
+ /**
117
+ * Check if a file is a Rust file
118
+ */
119
+ isRustFile(filePath: string): boolean {
120
+ return path.extname(filePath).toLowerCase() === ".rs";
121
+ }
122
+
123
+ /**
124
+ * Run cargo check on the project
125
+ */
126
+ checkFile(filePath: string, cwd: string): RustDiagnostic[] {
127
+ const cargoExe = this.findCargoPath();
128
+ if (!cargoExe) return [];
129
+
130
+ const absolutePath = path.resolve(filePath);
131
+ if (!fs.existsSync(absolutePath)) return [];
132
+
133
+ try {
134
+ const cargoCmd = cargoExe.includes(" ") ? `"${cargoExe}"` : cargoExe;
135
+ const result = spawnSync(
136
+ cargoCmd,
137
+ ["check", "--message-format", "json"],
138
+ {
139
+ encoding: "utf-8",
140
+ timeout: 60000,
141
+ cwd,
142
+ shell: true,
143
+ },
144
+ );
145
+
146
+ const output = result.stdout || "";
147
+ return this.parseJsonOutput(output, absolutePath);
148
+ } catch (err: any) {
149
+ this.log(`Check error: ${err.message}`);
150
+ return [];
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Run clippy for additional lints
156
+ */
157
+ clippyCheck(cwd: string): RustDiagnostic[] {
158
+ if (!this.isAvailable()) return [];
159
+
160
+ try {
161
+ const result = spawnSync(
162
+ "cargo",
163
+ ["clippy", "--message-format", "json"],
164
+ {
165
+ encoding: "utf-8",
166
+ timeout: 60000,
167
+ cwd,
168
+ shell: true,
169
+ },
170
+ );
171
+
172
+ const output = result.stdout || "";
173
+ return this.parseJsonOutput(output, "");
174
+ } catch (err: any) {
175
+ this.log(`Clippy error: ${err.message}`);
176
+ return [];
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Format diagnostics for LLM consumption
182
+ */
183
+ formatDiagnostics(diags: RustDiagnostic[], maxItems = 10): string {
184
+ if (diags.length === 0) return "";
185
+
186
+ const errors = diags.filter((d) => d.severity === "error");
187
+ const warnings = diags.filter((d) => d.severity === "warning");
188
+
189
+ let output = `[Rust] ${diags.length} issue(s)`;
190
+ if (errors.length) output += ` — ${errors.length} error(s)`;
191
+ if (warnings.length) output += ` — ${warnings.length} warning(s)`;
192
+ output += ":\n";
193
+
194
+ for (const d of diags.slice(0, maxItems)) {
195
+ const loc = `L${d.line}:${d.column}`;
196
+ const code = d.code ? ` [${d.code}]` : "";
197
+ output += ` [${d.severity}] ${loc} ${d.message.slice(0, 200)}${code}\n`;
198
+ }
199
+
200
+ if (diags.length > maxItems) {
201
+ output += ` ... and ${diags.length - maxItems} more\n`;
202
+ }
203
+
204
+ return output;
205
+ }
206
+
207
+ // --- Internal ---
208
+
209
+ private parseJsonOutput(
210
+ output: string,
211
+ filterFile: string,
212
+ ): RustDiagnostic[] {
213
+ if (!output.trim()) return [];
214
+
215
+ const diags: RustDiagnostic[] = [];
216
+ const lines = output.split("\n").filter((l) => l.trim());
217
+
218
+ for (const line of lines) {
219
+ try {
220
+ const msg: CargoMessage = JSON.parse(line);
221
+
222
+ if (msg.reason === "compiler-message" && msg.message) {
223
+ const { level, message, spans, code } = msg.message;
224
+
225
+ // Only include errors and warnings
226
+ if (level !== "error" && level !== "warning" && level !== "note") {
227
+ continue;
228
+ }
229
+
230
+ // Get location from spans
231
+ if (spans && spans.length > 0) {
232
+ for (const span of spans) {
233
+ const file = span.file_name;
234
+
235
+ // Filter to specific file if provided
236
+ if (
237
+ filterFile &&
238
+ path.resolve(file) !== path.resolve(filterFile)
239
+ ) {
240
+ continue;
241
+ }
242
+
243
+ diags.push({
244
+ line: span.line_start,
245
+ column: span.column_start - 1,
246
+ endLine: span.line_end,
247
+ endColumn: span.column_end - 1,
248
+ severity: level as RustDiagnostic["severity"],
249
+ message: message.slice(0, 300),
250
+ code,
251
+ file: path.resolve(file),
252
+ });
253
+ }
254
+ } else {
255
+ // No span info, add as general diagnostic
256
+ diags.push({
257
+ line: 1,
258
+ column: 0,
259
+ endLine: 1,
260
+ endColumn: 0,
261
+ severity: level as RustDiagnostic["severity"],
262
+ message: message.slice(0, 300),
263
+ code,
264
+ file: filterFile || "",
265
+ });
266
+ }
267
+ }
268
+ } catch (err) { void err; } // Skip non-JSON lines
269
+ }
270
+
271
+ return diags;
272
+ }
266
273
  }
@@ -15,8 +15,8 @@
15
15
  */
16
16
 
17
17
  import { spawnSync } from "node:child_process";
18
- import * as path from "node:path";
19
18
  import * as fs from "node:fs";
19
+ import * as path from "node:path";
20
20
 
21
21
  export interface Diagnostic {
22
22
  line: number;