pi-lens 3.2.0 → 3.3.1

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 (77) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -10
  3. package/clients/__tests__/file-time.test.js +216 -0
  4. package/clients/__tests__/format-service.test.js +245 -0
  5. package/clients/__tests__/formatters.test.js +271 -0
  6. package/clients/agent-behavior-client.test.js +94 -0
  7. package/clients/biome-client.test.js +144 -0
  8. package/clients/cache-manager.test.js +197 -0
  9. package/clients/complexity-client.test.js +234 -0
  10. package/clients/dependency-checker.test.js +60 -0
  11. package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
  12. package/clients/dispatch/__tests__/runner-registration.test.js +234 -0
  13. package/clients/dispatch/__tests__/runner-registration.test.ts +2 -2
  14. package/clients/dispatch/dispatcher.edge.test.js +82 -0
  15. package/clients/dispatch/dispatcher.format.test.js +46 -0
  16. package/clients/dispatch/dispatcher.inline.test.js +74 -0
  17. package/clients/dispatch/dispatcher.test.js +116 -0
  18. package/clients/dispatch/runners/architect.test.js +138 -0
  19. package/clients/dispatch/runners/ast-grep-napi.test.js +106 -0
  20. package/clients/dispatch/runners/lsp.js +42 -5
  21. package/clients/dispatch/runners/oxlint.test.js +230 -0
  22. package/clients/dispatch/runners/pyright.test.js +98 -0
  23. package/clients/dispatch/runners/python-slop.test.js +203 -0
  24. package/clients/dispatch/runners/scan_codebase.test.js +89 -0
  25. package/clients/dispatch/runners/shellcheck.test.js +98 -0
  26. package/clients/dispatch/runners/spellcheck.test.js +158 -0
  27. package/clients/dispatch/utils/format-utils.js +1 -6
  28. package/clients/dispatch/utils/format-utils.ts +1 -6
  29. package/clients/dogfood.test.js +201 -0
  30. package/clients/file-kinds.test.js +169 -0
  31. package/clients/formatters.js +1 -1
  32. package/clients/go-client.test.js +127 -0
  33. package/clients/jscpd-client.test.js +127 -0
  34. package/clients/knip-client.test.js +112 -0
  35. package/clients/lsp/__tests__/client.test.js +310 -0
  36. package/clients/lsp/__tests__/client.test.ts +1 -46
  37. package/clients/lsp/__tests__/config.test.js +167 -0
  38. package/clients/lsp/__tests__/error-recovery.test.js +213 -0
  39. package/clients/lsp/__tests__/integration.test.js +127 -0
  40. package/clients/lsp/__tests__/launch.test.js +313 -0
  41. package/clients/lsp/__tests__/server.test.js +259 -0
  42. package/clients/lsp/__tests__/service.test.js +435 -0
  43. package/clients/lsp/client.js +32 -44
  44. package/clients/lsp/client.ts +36 -45
  45. package/clients/lsp/launch.js +11 -6
  46. package/clients/lsp/launch.ts +11 -6
  47. package/clients/lsp/server.js +27 -2
  48. package/clients/metrics-client.test.js +141 -0
  49. package/clients/ruff-client.test.js +132 -0
  50. package/clients/rust-client.test.js +108 -0
  51. package/clients/sanitize.test.js +177 -0
  52. package/clients/secrets-scanner.test.js +100 -0
  53. package/clients/test-runner-client.test.js +192 -0
  54. package/clients/todo-scanner.test.js +301 -0
  55. package/clients/type-coverage-client.test.js +105 -0
  56. package/clients/typescript-client.codefix.test.js +157 -0
  57. package/clients/typescript-client.test.js +105 -0
  58. package/commands/rate.test.js +119 -0
  59. package/index.ts +66 -72
  60. package/package.json +1 -1
  61. package/clients/bus/bus.js +0 -191
  62. package/clients/bus/bus.ts +0 -251
  63. package/clients/bus/events.js +0 -214
  64. package/clients/bus/events.ts +0 -279
  65. package/clients/bus/index.js +0 -8
  66. package/clients/bus/index.ts +0 -9
  67. package/clients/bus/integration.js +0 -158
  68. package/clients/bus/integration.ts +0 -227
  69. package/clients/dispatch/bus-dispatcher.js +0 -178
  70. package/clients/dispatch/bus-dispatcher.ts +0 -258
  71. package/clients/services/__tests__/effect-integration.test.ts +0 -111
  72. package/clients/services/effect-integration.js +0 -198
  73. package/clients/services/effect-integration.ts +0 -276
  74. package/clients/services/index.js +0 -7
  75. package/clients/services/index.ts +0 -8
  76. package/clients/services/runner-service.js +0 -134
  77. package/clients/services/runner-service.ts +0 -225
@@ -0,0 +1,301 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { createTempFile, setupTestEnvironment } from "./test-utils.js";
3
+ import { TodoScanner } from "./todo-scanner.js";
4
+ describe("TodoScanner", () => {
5
+ let client;
6
+ let tmpDir;
7
+ let cleanup;
8
+ beforeEach(() => {
9
+ client = new TodoScanner();
10
+ ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-todo-test-"));
11
+ });
12
+ afterEach(() => {
13
+ cleanup();
14
+ });
15
+ describe("scanFile", () => {
16
+ it("should return empty array for non-existent files", () => {
17
+ const result = client.scanFile("/nonexistent/file.ts");
18
+ expect(result).toEqual([]);
19
+ });
20
+ it("should find TODO comments", () => {
21
+ const content = `
22
+ // TODO: implement this function
23
+ function foo() {}
24
+ `;
25
+ const filePath = createTempFile(tmpDir, "test.ts", content);
26
+ const result = client.scanFile(filePath);
27
+ expect(result.length).toBe(1);
28
+ expect(result[0].type).toBe("TODO");
29
+ expect(result[0].message).toContain("implement this function");
30
+ });
31
+ it("should find FIXME comments", () => {
32
+ const content = `
33
+ // FIXME: this is broken
34
+ function foo() {}
35
+ `;
36
+ const filePath = createTempFile(tmpDir, "test.ts", content);
37
+ const result = client.scanFile(filePath);
38
+ expect(result.length).toBe(1);
39
+ expect(result[0].type).toBe("FIXME");
40
+ });
41
+ it("should find HACK comments", () => {
42
+ const content = `
43
+ // HACK: temporary workaround
44
+ const x = 1;
45
+ `;
46
+ const filePath = createTempFile(tmpDir, "test.ts", content);
47
+ const result = client.scanFile(filePath);
48
+ expect(result.length).toBe(1);
49
+ expect(result[0].type).toBe("HACK");
50
+ });
51
+ it("should find BUG comments", () => {
52
+ const content = `
53
+ // BUG: this causes a crash
54
+ const x = 1;
55
+ `;
56
+ const filePath = createTempFile(tmpDir, "test.ts", content);
57
+ const result = client.scanFile(filePath);
58
+ expect(result.length).toBe(1);
59
+ expect(result[0].type).toBe("BUG");
60
+ });
61
+ it("should find NOTE comments", () => {
62
+ const content = `
63
+ // NOTE: important design decision
64
+ const x = 1;
65
+ `;
66
+ const filePath = createTempFile(tmpDir, "test.ts", content);
67
+ const result = client.scanFile(filePath);
68
+ expect(result.length).toBe(1);
69
+ expect(result[0].type).toBe("NOTE");
70
+ });
71
+ it("should find TODO in block comments", () => {
72
+ const content = `
73
+ /*
74
+ * TODO: refactor this later
75
+ */
76
+ const x = 1;
77
+ `;
78
+ const filePath = createTempFile(tmpDir, "test.ts", content);
79
+ const result = client.scanFile(filePath);
80
+ expect(result.length).toBe(1);
81
+ expect(result[0].type).toBe("TODO");
82
+ });
83
+ it("should find TODO in JSDoc comments", () => {
84
+ const content = `
85
+ /**
86
+ * TODO: add proper documentation
87
+ */
88
+ function foo() {}
89
+ `;
90
+ const filePath = createTempFile(tmpDir, "test.ts", content);
91
+ const result = client.scanFile(filePath);
92
+ expect(result.length).toBe(1);
93
+ expect(result[0].type).toBe("TODO");
94
+ });
95
+ it("should find TODO in Python comments", () => {
96
+ const content = `
97
+ # TODO: implement this
98
+ def foo():
99
+ pass
100
+ `;
101
+ const filePath = createTempFile(tmpDir, "test.py", content);
102
+ const result = client.scanFile(filePath);
103
+ expect(result.length).toBe(1);
104
+ expect(result[0].type).toBe("TODO");
105
+ });
106
+ it("should skip TODO in strings", () => {
107
+ const content = `
108
+ const message = "TODO: buy milk";
109
+ `;
110
+ const filePath = createTempFile(tmpDir, "test.ts", content);
111
+ const result = client.scanFile(filePath);
112
+ expect(result.length).toBe(0);
113
+ });
114
+ it("should report correct line numbers", () => {
115
+ const content = `
116
+ const x = 1;
117
+ const y = 2;
118
+ // TODO: fix this
119
+ const z = 3;
120
+ `;
121
+ const filePath = createTempFile(tmpDir, "test.ts", content);
122
+ const result = client.scanFile(filePath);
123
+ expect(result[0].line).toBe(4);
124
+ });
125
+ it("should find multiple annotations in one file", () => {
126
+ const content = `
127
+ // TODO: first
128
+ // FIXME: second
129
+ // HACK: third
130
+ `;
131
+ const filePath = createTempFile(tmpDir, "test.ts", content);
132
+ const result = client.scanFile(filePath);
133
+ expect(result.length).toBe(3);
134
+ });
135
+ });
136
+ describe("scanDirectory", () => {
137
+ it("should scan all files in directory", () => {
138
+ createTempFile(tmpDir, "file1.ts", "// TODO: task 1");
139
+ createTempFile(tmpDir, "file2.ts", "// FIXME: bug 1");
140
+ createTempFile(tmpDir, "file3.py", "# HACK: workaround");
141
+ const result = client.scanDirectory(tmpDir);
142
+ expect(result.items.length).toBe(3);
143
+ });
144
+ it("should skip node_modules", () => {
145
+ createTempFile(tmpDir, "src/file.ts", "// TODO: task 1");
146
+ createTempFile(tmpDir, "node_modules/lib/file.ts", "// TODO: should be skipped");
147
+ const result = client.scanDirectory(tmpDir);
148
+ expect(result.items.length).toBe(1);
149
+ expect(result.items[0].file).not.toContain("node_modules");
150
+ });
151
+ it("should group items by type", () => {
152
+ createTempFile(tmpDir, "file1.ts", "// TODO: task 1");
153
+ createTempFile(tmpDir, "file2.ts", "// TODO: task 2");
154
+ createTempFile(tmpDir, "file3.ts", "// FIXME: bug 1");
155
+ const result = client.scanDirectory(tmpDir);
156
+ expect(result.byType.get("TODO")?.length).toBe(2);
157
+ expect(result.byType.get("FIXME")?.length).toBe(1);
158
+ });
159
+ it("should group items by file", () => {
160
+ createTempFile(tmpDir, "file1.ts", "// TODO: task 1\n// FIXME: bug 1");
161
+ createTempFile(tmpDir, "file2.ts", "// TODO: task 2");
162
+ const result = client.scanDirectory(tmpDir);
163
+ const file1Items = [...result.byFile.entries()].find(([k]) => k.includes("file1.ts"));
164
+ expect(file1Items?.[1].length).toBe(2);
165
+ });
166
+ });
167
+ describe("formatResult", () => {
168
+ it("should return empty string for no results", () => {
169
+ const result = { items: [], byType: new Map(), byFile: new Map() };
170
+ expect(client.formatResult(result)).toBe("");
171
+ });
172
+ it("should format results with counts", () => {
173
+ const result = {
174
+ items: [
175
+ {
176
+ type: "TODO",
177
+ message: "task 1",
178
+ file: "test.ts",
179
+ line: 1,
180
+ column: 0,
181
+ },
182
+ {
183
+ type: "FIXME",
184
+ message: "bug 1",
185
+ file: "test.ts",
186
+ line: 2,
187
+ column: 0,
188
+ },
189
+ ],
190
+ byType: new Map([
191
+ [
192
+ "TODO",
193
+ [
194
+ {
195
+ type: "TODO",
196
+ message: "task 1",
197
+ file: "test.ts",
198
+ line: 1,
199
+ column: 0,
200
+ },
201
+ ],
202
+ ],
203
+ ]),
204
+ byFile: new Map([
205
+ [
206
+ "test.ts",
207
+ [
208
+ {
209
+ type: "TODO",
210
+ message: "task 1",
211
+ file: "test.ts",
212
+ line: 1,
213
+ column: 0,
214
+ },
215
+ ],
216
+ ],
217
+ ]),
218
+ };
219
+ const formatted = client.formatResult(result);
220
+ expect(formatted).toContain("2 annotation(s)");
221
+ expect(formatted).toContain("TODO");
222
+ expect(formatted).toContain("FIXME");
223
+ });
224
+ it("should prioritize FIXME/HACK before TODO", () => {
225
+ const result = {
226
+ items: [
227
+ {
228
+ type: "FIXME",
229
+ message: "bug",
230
+ file: "test.ts",
231
+ line: 2,
232
+ column: 0,
233
+ },
234
+ {
235
+ type: "TODO",
236
+ message: "task",
237
+ file: "test.ts",
238
+ line: 1,
239
+ column: 0,
240
+ },
241
+ ],
242
+ byType: new Map(),
243
+ byFile: new Map(),
244
+ };
245
+ const formatted = client.formatResult(result);
246
+ // Check that FIXME line comes before TODO line in the sorted output
247
+ const fixmeLineIndex = formatted.indexOf("🔴");
248
+ const todoLineIndex = formatted.indexOf("📝");
249
+ expect(fixmeLineIndex).toBeLessThan(todoLineIndex);
250
+ });
251
+ it("should show correct icons", () => {
252
+ const result = {
253
+ items: [
254
+ {
255
+ type: "FIXME",
256
+ message: "bug",
257
+ file: "test.ts",
258
+ line: 1,
259
+ column: 0,
260
+ },
261
+ {
262
+ type: "HACK",
263
+ message: "hack",
264
+ file: "test.ts",
265
+ line: 2,
266
+ column: 0,
267
+ },
268
+ {
269
+ type: "BUG",
270
+ message: "bug",
271
+ file: "test.ts",
272
+ line: 3,
273
+ column: 0,
274
+ },
275
+ {
276
+ type: "TODO",
277
+ message: "todo",
278
+ file: "test.ts",
279
+ line: 4,
280
+ column: 0,
281
+ },
282
+ {
283
+ type: "NOTE",
284
+ message: "note",
285
+ file: "test.ts",
286
+ line: 5,
287
+ column: 0,
288
+ },
289
+ ],
290
+ byType: new Map(),
291
+ byFile: new Map(),
292
+ };
293
+ const formatted = client.formatResult(result);
294
+ expect(formatted).toContain("🔴"); // FIXME
295
+ expect(formatted).toContain("🟠"); // HACK
296
+ expect(formatted).toContain("🐛"); // BUG
297
+ expect(formatted).toContain("📝"); // TODO
298
+ expect(formatted).toContain("ℹ️"); // NOTE
299
+ });
300
+ });
301
+ });
@@ -0,0 +1,105 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { setupTestEnvironment } from "./test-utils.js";
3
+ import { TypeCoverageClient } from "./type-coverage-client.js";
4
+ describe("TypeCoverageClient", () => {
5
+ let client;
6
+ let _tmpDir;
7
+ let cleanup;
8
+ beforeEach(() => {
9
+ client = new TypeCoverageClient();
10
+ ({ tmpDir: _tmpDir, cleanup } = setupTestEnvironment("pi-lens-typecoverage-test-"));
11
+ });
12
+ afterEach(() => {
13
+ cleanup();
14
+ });
15
+ afterEach(() => {
16
+ cleanup();
17
+ });
18
+ describe("isAvailable", () => {
19
+ it("should check type-coverage availability", () => {
20
+ const available = client.isAvailable();
21
+ expect(typeof available).toBe("boolean");
22
+ });
23
+ });
24
+ describe("formatResult", () => {
25
+ it("should return empty string when not successful", () => {
26
+ const result = {
27
+ success: false,
28
+ percentage: 0,
29
+ typed: 0,
30
+ total: 0,
31
+ untypedLocations: [],
32
+ };
33
+ expect(client.formatResult(result)).toBe("");
34
+ });
35
+ it("should show coverage percentage", () => {
36
+ const result = {
37
+ success: true,
38
+ percentage: 95,
39
+ typed: 95,
40
+ total: 100,
41
+ untypedLocations: [],
42
+ };
43
+ const formatted = client.formatResult(result);
44
+ expect(formatted).toContain("95.0%");
45
+ expect(formatted).toContain("95/100");
46
+ });
47
+ it("should show warning for low coverage", () => {
48
+ const result = {
49
+ success: true,
50
+ percentage: 80,
51
+ typed: 80,
52
+ total: 100,
53
+ untypedLocations: [],
54
+ };
55
+ const formatted = client.formatResult(result);
56
+ expect(formatted).toContain("⚠");
57
+ });
58
+ it("should show checkmark for high coverage", () => {
59
+ const result = {
60
+ success: true,
61
+ percentage: 100,
62
+ typed: 100,
63
+ total: 100,
64
+ untypedLocations: [],
65
+ };
66
+ const formatted = client.formatResult(result);
67
+ expect(formatted).toContain("✓");
68
+ });
69
+ it("should show untyped locations", () => {
70
+ const result = {
71
+ success: true,
72
+ percentage: 90,
73
+ typed: 90,
74
+ total: 100,
75
+ untypedLocations: [
76
+ { file: "test.ts", line: 10, column: 5, name: "x" },
77
+ { file: "test.ts", line: 20, column: 8, name: "y" },
78
+ ],
79
+ };
80
+ const formatted = client.formatResult(result);
81
+ expect(formatted).toContain("test.ts:10");
82
+ expect(formatted).toContain("test.ts:20");
83
+ expect(formatted).toContain("x");
84
+ expect(formatted).toContain("y");
85
+ });
86
+ it("should truncate long untyped location lists", () => {
87
+ const locations = Array.from({ length: 20 }, (_, i) => ({
88
+ file: `file${i}.ts`,
89
+ line: i + 1,
90
+ column: 0,
91
+ name: `var${i}`,
92
+ }));
93
+ const result = {
94
+ success: true,
95
+ percentage: 80,
96
+ typed: 80,
97
+ total: 100,
98
+ untypedLocations: locations,
99
+ };
100
+ const formatted = client.formatResult(result, 10);
101
+ expect(formatted).toContain("...");
102
+ expect(formatted).toContain("10 more");
103
+ });
104
+ });
105
+ });
@@ -0,0 +1,157 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { createTempFile, setupTestEnvironment } from "./test-utils.js";
3
+ import { TypeScriptClient } from "./typescript-client.js";
4
+ describe("TypeScriptClient - Code Fixes", () => {
5
+ let client;
6
+ let tmpDir;
7
+ let cleanup;
8
+ beforeEach(() => {
9
+ client = new TypeScriptClient();
10
+ ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-codefix-test-"));
11
+ });
12
+ afterEach(() => {
13
+ cleanup();
14
+ });
15
+ describe("getCodeFixes", () => {
16
+ it("should provide fix for missing property on object literal", () => {
17
+ // Real-world case: Missing required property in object literal
18
+ const content = `
19
+ interface Config {
20
+ name: string;
21
+ port: number;
22
+ debug: boolean;
23
+ }
24
+
25
+ const config: Config = {
26
+ name: "my-app",
27
+ port: 3000
28
+ // Missing 'debug' property - TS2345
29
+ };
30
+ `;
31
+ const filePath = createTempFile(tmpDir, "missing-property.ts", content);
32
+ client.addFile(filePath, content);
33
+ const diags = client.getDiagnostics(filePath);
34
+ const missingPropError = diags.find((d) => d.code === 2345 || d.message.includes("missing"));
35
+ if (missingPropError) {
36
+ const line = missingPropError.range.start.line;
37
+ const char = missingPropError.range.start.character;
38
+ const fixes = client.getCodeFixes(filePath, line, char, [
39
+ missingPropError.code,
40
+ ]);
41
+ // TypeScript should suggest adding the missing property
42
+ expect(fixes.length).toBeGreaterThan(0);
43
+ const hasAddPropertyFix = fixes.some((f) => f.description.toLowerCase().includes("add") ||
44
+ f.description.toLowerCase().includes("property") ||
45
+ f.description.toLowerCase().includes("declare"));
46
+ expect(hasAddPropertyFix).toBe(true);
47
+ }
48
+ });
49
+ it("should provide fix for missing await in async function", () => {
50
+ // Real-world case: Forgetting await on a Promise-returning function
51
+ const content = `
52
+ async function fetchUser(id: string): Promise<{ name: string }> {
53
+ return { name: "John" };
54
+ }
55
+
56
+ async function getUserName(id: string): Promise<string> {
57
+ const user = fetchUser(id); // Missing await
58
+ return user.name; // Type 'Promise<{ name: string; }>' has no property 'name'
59
+ }
60
+ `;
61
+ const filePath = createTempFile(tmpDir, "missing-await.ts", content);
62
+ client.addFile(filePath, content);
63
+ const diags = client.getDiagnostics(filePath);
64
+ // TS2739: Type 'Promise<{ name: string; }>' is missing 'name'
65
+ const propertyError = diags.find((d) => d.code === 2739 || d.message.includes("is missing"));
66
+ if (propertyError) {
67
+ const fixes = client.getAllCodeFixes(filePath);
68
+ // If there's an error, check if we have fixes for it
69
+ const lineFixes = fixes.get(propertyError.range.start.line);
70
+ if (lineFixes) {
71
+ expect(lineFixes.length).toBeGreaterThan(0);
72
+ }
73
+ }
74
+ // Test passes if we get here - not all TS versions provide fixes for this
75
+ expect(true).toBe(true);
76
+ });
77
+ it("should provide fix for incorrect type assignment", () => {
78
+ // Real-world case: String instead of number
79
+ const content = `
80
+ function calculateTotal(price: number, tax: number): number {
81
+ return price + tax;
82
+ }
83
+
84
+ const result = calculateTotal("100", 10); // TS2345: Argument of type 'string' is not assignable to parameter of type 'number'
85
+ `;
86
+ const filePath = createTempFile(tmpDir, "type-mismatch.ts", content);
87
+ client.addFile(filePath, content);
88
+ const diags = client.getDiagnostics(filePath);
89
+ const typeError = diags.find((d) => d.code === 2345);
90
+ if (typeError) {
91
+ const line = typeError.range.start.line;
92
+ const char = typeError.range.start.character;
93
+ const fixes = client.getCodeFixes(filePath, line, char, [2345]);
94
+ // TypeScript often suggests fixes for type mismatches
95
+ expect(fixes).toBeDefined();
96
+ }
97
+ });
98
+ it("should collect all fixes via getAllCodeFixes", () => {
99
+ // Multiple errors in one file
100
+ const content = `
101
+ interface Person {
102
+ name: string;
103
+ age: number;
104
+ }
105
+
106
+ const person: Person = {
107
+ name: "Alice"
108
+ // Missing age
109
+ };
110
+
111
+ function greet(p: Person): string {
112
+ return "Hello " + p.name;
113
+ }
114
+
115
+ greet({ name: "Bob" }); // Missing age in argument
116
+ `;
117
+ const filePath = createTempFile(tmpDir, "multiple-errors.ts", content);
118
+ client.addFile(filePath, content);
119
+ const allFixes = client.getAllCodeFixes(filePath);
120
+ // Should have fixes mapped by line number
121
+ expect(allFixes).toBeInstanceOf(Map);
122
+ // Each fix entry should have a description and changes
123
+ for (const [line, fixes] of allFixes.entries()) {
124
+ expect(typeof line).toBe("number");
125
+ expect(fixes.length).toBeGreaterThan(0);
126
+ for (const fix of fixes) {
127
+ expect(fix.description).toBeTruthy();
128
+ expect(fix.changes).toBeDefined();
129
+ }
130
+ }
131
+ });
132
+ });
133
+ describe("Integration with diagnostic messages", () => {
134
+ it("should include fix suggestions in getAllCodeFixes output", () => {
135
+ const content = `
136
+ class User {
137
+ constructor(public name: string) {}
138
+ }
139
+
140
+ const user = new User(); // TS2554: Expected 1 arguments, but got 0
141
+ `;
142
+ const filePath = createTempFile(tmpDir, "constructor-args.ts", content);
143
+ client.addFile(filePath, content);
144
+ const diags = client.getDiagnostics(filePath);
145
+ const argError = diags.find((d) => d.code === 2554);
146
+ if (argError) {
147
+ const fixes = client.getAllCodeFixes(filePath);
148
+ const lineFixes = fixes.get(argError.range.start.line);
149
+ if (lineFixes && lineFixes.length > 0) {
150
+ // The runner would append this to the message
151
+ const suggestion = lineFixes[0].description;
152
+ expect(suggestion).toBeTruthy();
153
+ }
154
+ }
155
+ });
156
+ });
157
+ });
@@ -0,0 +1,105 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { createTempFile, setupTestEnvironment } from "./test-utils.js";
3
+ import { TypeScriptClient } from "./typescript-client.js";
4
+ describe("TypeScriptClient", () => {
5
+ let client;
6
+ let tmpDir;
7
+ let cleanup;
8
+ beforeEach(() => {
9
+ client = new TypeScriptClient();
10
+ ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-ts-test-"));
11
+ });
12
+ afterEach(() => {
13
+ cleanup();
14
+ });
15
+ afterEach(() => {
16
+ cleanup();
17
+ });
18
+ describe("isTypeScriptFile", () => {
19
+ it("should recognize TypeScript files", () => {
20
+ expect(client.isTypeScriptFile("test.ts")).toBe(true);
21
+ expect(client.isTypeScriptFile("test.tsx")).toBe(true);
22
+ });
23
+ it("should also recognize JavaScript files (for type checking)", () => {
24
+ expect(client.isTypeScriptFile("test.js")).toBe(true);
25
+ expect(client.isTypeScriptFile("test.jsx")).toBe(true);
26
+ });
27
+ it("should not recognize non-JS/TS files", () => {
28
+ expect(client.isTypeScriptFile("test.py")).toBe(false);
29
+ expect(client.isTypeScriptFile("test.md")).toBe(false);
30
+ });
31
+ });
32
+ describe("updateFile and getDiagnostics", () => {
33
+ it("should detect type errors", () => {
34
+ const content = `
35
+ const x: number = "string"; // Type error
36
+ `;
37
+ const filePath = createTempFile(tmpDir, "test.ts", content);
38
+ client.updateFile(filePath, content);
39
+ const diags = client.getDiagnostics(filePath);
40
+ expect(diags.length).toBeGreaterThan(0);
41
+ expect(diags.some((d) => d.message.includes("string"))).toBe(true);
42
+ });
43
+ it("should not report errors for valid code", () => {
44
+ const content = `
45
+ const x: number = 42;
46
+ const y: string = "hello";
47
+ `;
48
+ const filePath = createTempFile(tmpDir, "test.ts", content);
49
+ client.updateFile(filePath, content);
50
+ const diags = client.getDiagnostics(filePath);
51
+ // May have warnings but no errors for valid code
52
+ const errors = diags.filter((d) => d.severity === 1); // Error severity
53
+ expect(errors.length).toBe(0);
54
+ });
55
+ it("should detect undefined variables", () => {
56
+ const content = `
57
+ function test() {
58
+ return undefinedVariable;
59
+ }
60
+ `;
61
+ const filePath = createTempFile(tmpDir, "test.ts", content);
62
+ client.updateFile(filePath, content);
63
+ const diags = client.getDiagnostics(filePath);
64
+ expect(diags.some((d) => d.message.includes("undefined"))).toBe(true);
65
+ });
66
+ it("should detect missing function arguments", () => {
67
+ const content = `
68
+ function add(a: number, b: number): number {
69
+ return a + b;
70
+ }
71
+ const result = add(1);
72
+ `;
73
+ const filePath = createTempFile(tmpDir, "test.ts", content);
74
+ client.updateFile(filePath, content);
75
+ const diags = client.getDiagnostics(filePath);
76
+ expect(diags.some((d) => d.message.includes("Expected"))).toBe(true);
77
+ });
78
+ });
79
+ describe("diagnostic severity", () => {
80
+ it("should have correct severity levels", () => {
81
+ const diags = [
82
+ {
83
+ range: {
84
+ start: { line: 0, character: 0 },
85
+ end: { line: 0, character: 10 },
86
+ },
87
+ severity: 1, // Error
88
+ message: "Error message",
89
+ },
90
+ {
91
+ range: {
92
+ start: { line: 1, character: 0 },
93
+ end: { line: 1, character: 10 },
94
+ },
95
+ severity: 2, // Warning
96
+ message: "Warning message",
97
+ },
98
+ ];
99
+ // Test that diagnostics have expected structure
100
+ expect(diags[0].severity).toBe(1); // Error
101
+ expect(diags[1].severity).toBe(2); // Warning
102
+ expect(diags[0].message).toContain("Error");
103
+ });
104
+ });
105
+ });