pi-lens 3.2.0 → 3.3.0

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 (75) hide show
  1. package/CHANGELOG.md +12 -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/server.js +27 -2
  46. package/clients/metrics-client.test.js +141 -0
  47. package/clients/ruff-client.test.js +132 -0
  48. package/clients/rust-client.test.js +108 -0
  49. package/clients/sanitize.test.js +177 -0
  50. package/clients/secrets-scanner.test.js +100 -0
  51. package/clients/test-runner-client.test.js +192 -0
  52. package/clients/todo-scanner.test.js +301 -0
  53. package/clients/type-coverage-client.test.js +105 -0
  54. package/clients/typescript-client.codefix.test.js +157 -0
  55. package/clients/typescript-client.test.js +105 -0
  56. package/commands/rate.test.js +119 -0
  57. package/index.ts +66 -72
  58. package/package.json +1 -1
  59. package/clients/bus/bus.js +0 -191
  60. package/clients/bus/bus.ts +0 -251
  61. package/clients/bus/events.js +0 -214
  62. package/clients/bus/events.ts +0 -279
  63. package/clients/bus/index.js +0 -8
  64. package/clients/bus/index.ts +0 -9
  65. package/clients/bus/integration.js +0 -158
  66. package/clients/bus/integration.ts +0 -227
  67. package/clients/dispatch/bus-dispatcher.js +0 -178
  68. package/clients/dispatch/bus-dispatcher.ts +0 -258
  69. package/clients/services/__tests__/effect-integration.test.ts +0 -111
  70. package/clients/services/effect-integration.js +0 -198
  71. package/clients/services/effect-integration.ts +0 -276
  72. package/clients/services/index.js +0 -7
  73. package/clients/services/index.ts +0 -8
  74. package/clients/services/runner-service.js +0 -134
  75. package/clients/services/runner-service.ts +0 -225
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Formatter Detection Tests
3
+ *
4
+ * Tests smart detection of formatters based on:
5
+ * - Config files (biome.json, .prettierrc, etc.)
6
+ * - Dependencies (package.json, requirements.txt)
7
+ * - Binary availability
8
+ */
9
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
10
+ import * as fs from "node:fs";
11
+ import * as path from "node:path";
12
+ import { biomeFormatter, prettierFormatter, ruffFormatter, blackFormatter, gofmtFormatter, rustfmtFormatter, getFormattersForFile, clearFormatterCache, formatFile, } from "../formatters.js";
13
+ import { fileURLToPath } from "url";
14
+ import { dirname } from "path";
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ const TEST_DIR = path.join(__dirname, "..", "..", "test-formatters");
18
+ describe("Formatter Detection", () => {
19
+ beforeEach(() => {
20
+ clearFormatterCache();
21
+ if (fs.existsSync(TEST_DIR)) {
22
+ fs.rmSync(TEST_DIR, { recursive: true });
23
+ }
24
+ fs.mkdirSync(TEST_DIR, { recursive: true });
25
+ });
26
+ afterEach(() => {
27
+ clearFormatterCache();
28
+ if (fs.existsSync(TEST_DIR)) {
29
+ fs.rmSync(TEST_DIR, { recursive: true });
30
+ }
31
+ });
32
+ describe("biomeFormatter.detect()", () => {
33
+ it("should detect biome.json config file", async () => {
34
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), '{"formatter": {}}');
35
+ const detected = await biomeFormatter.detect(TEST_DIR);
36
+ expect(detected).toBe(true);
37
+ });
38
+ it("should detect biome.jsonc config file", async () => {
39
+ fs.writeFileSync(path.join(TEST_DIR, "biome.jsonc"), "{}");
40
+ const detected = await biomeFormatter.detect(TEST_DIR);
41
+ expect(detected).toBe(true);
42
+ });
43
+ it("should detect @biomejs/biome in devDependencies", async () => {
44
+ fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { "@biomejs/biome": "^1.0.0" } }));
45
+ const detected = await biomeFormatter.detect(TEST_DIR);
46
+ expect(detected).toBe(true);
47
+ });
48
+ it("should return false when no biome config", async () => {
49
+ const detected = await biomeFormatter.detect(TEST_DIR);
50
+ expect(detected).toBe(false);
51
+ });
52
+ it("should find biome.json in parent directory", async () => {
53
+ const subDir = path.join(TEST_DIR, "src", "components");
54
+ fs.mkdirSync(subDir, { recursive: true });
55
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
56
+ const detected = await biomeFormatter.detect(subDir);
57
+ expect(detected).toBe(true);
58
+ });
59
+ });
60
+ describe("prettierFormatter.detect()", () => {
61
+ it("should detect .prettierrc config file", async () => {
62
+ fs.writeFileSync(path.join(TEST_DIR, ".prettierrc"), "{}");
63
+ const detected = await prettierFormatter.detect(TEST_DIR);
64
+ expect(detected).toBe(true);
65
+ });
66
+ it("should detect prettier in devDependencies", async () => {
67
+ fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { prettier: "^3.0.0" } }));
68
+ const detected = await prettierFormatter.detect(TEST_DIR);
69
+ expect(detected).toBe(true);
70
+ });
71
+ it("should detect prettier field in package.json", async () => {
72
+ fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ prettier: { semi: false } }));
73
+ const detected = await prettierFormatter.detect(TEST_DIR);
74
+ expect(detected).toBe(true);
75
+ });
76
+ it("should return false when no prettier config", async () => {
77
+ const detected = await prettierFormatter.detect(TEST_DIR);
78
+ expect(detected).toBe(false);
79
+ });
80
+ });
81
+ describe("ruffFormatter.detect()", () => {
82
+ it("should detect [tool.ruff] in pyproject.toml", async () => {
83
+ fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.ruff]\nline-length = 100");
84
+ const detected = await ruffFormatter.detect(TEST_DIR);
85
+ expect(detected).toBe(true);
86
+ });
87
+ it("should detect ruff.toml config file", async () => {
88
+ fs.writeFileSync(path.join(TEST_DIR, "ruff.toml"), "line-length = 100");
89
+ const detected = await ruffFormatter.detect(TEST_DIR);
90
+ expect(detected).toBe(true);
91
+ });
92
+ it("should detect .ruff.toml config file", async () => {
93
+ fs.writeFileSync(path.join(TEST_DIR, ".ruff.toml"), "line-length = 100");
94
+ const detected = await ruffFormatter.detect(TEST_DIR);
95
+ expect(detected).toBe(true);
96
+ });
97
+ it("should detect ruff in requirements.txt", async () => {
98
+ fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "ruff==0.1.0\n");
99
+ const detected = await ruffFormatter.detect(TEST_DIR);
100
+ expect(detected).toBe(true);
101
+ });
102
+ it("should detect ruff even if [tool.black] exists (no preference logic)", async () => {
103
+ // Create pyproject.toml with black config
104
+ fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.black]\nline-length = 100");
105
+ // Also write requirements with ruff
106
+ fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "ruff\n");
107
+ // The current implementation doesn't have preference logic
108
+ // Both black and ruff would be detected if their configs exist
109
+ // This is intentional - users can disable one if needed
110
+ const detected = await blackFormatter.detect(TEST_DIR);
111
+ expect(detected).toBe(true);
112
+ });
113
+ });
114
+ describe("blackFormatter.detect()", () => {
115
+ it("should detect [tool.black] in pyproject.toml", async () => {
116
+ fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.black]\nline-length = 100");
117
+ const detected = await blackFormatter.detect(TEST_DIR);
118
+ expect(detected).toBe(true);
119
+ });
120
+ it("should detect black in requirements.txt", async () => {
121
+ fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "black==23.0.0\n");
122
+ const detected = await blackFormatter.detect(TEST_DIR);
123
+ expect(detected).toBe(true);
124
+ });
125
+ it("should return false when no black config", async () => {
126
+ const detected = await blackFormatter.detect(TEST_DIR);
127
+ expect(detected).toBe(false);
128
+ });
129
+ });
130
+ describe("gofmtFormatter.detect()", () => {
131
+ it("should detect gofmt binary availability", async () => {
132
+ // This test depends on whether gofmt is installed
133
+ // We can't reliably test this in CI, but we can verify the logic
134
+ const detected = await gofmtFormatter.detect(TEST_DIR);
135
+ // Should return boolean based on binary availability
136
+ expect(typeof detected).toBe("boolean");
137
+ });
138
+ });
139
+ describe("rustfmtFormatter.detect()", () => {
140
+ it("should detect rustfmt binary availability", async () => {
141
+ const detected = await rustfmtFormatter.detect(TEST_DIR);
142
+ expect(typeof detected).toBe("boolean");
143
+ });
144
+ });
145
+ describe("getFormattersForFile()", () => {
146
+ it("should return formatters for TypeScript file with biome config", async () => {
147
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
148
+ const tsFile = path.join(TEST_DIR, "test.ts");
149
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
150
+ expect(formatters.map(f => f.name)).toContain("biome");
151
+ });
152
+ it("should return formatters for TypeScript file with prettier", async () => {
153
+ fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { prettier: "^3.0.0" } }));
154
+ const tsFile = path.join(TEST_DIR, "test.ts");
155
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
156
+ expect(formatters.map(f => f.name)).toContain("prettier");
157
+ });
158
+ it("should return multiple formatters for TypeScript file", async () => {
159
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
160
+ fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { prettier: "^3.0.0" } }));
161
+ const tsFile = path.join(TEST_DIR, "test.ts");
162
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
163
+ // Both biome and prettier should be returned
164
+ expect(formatters.length).toBeGreaterThanOrEqual(1);
165
+ const names = formatters.map(f => f.name);
166
+ expect(names).toContain("biome");
167
+ });
168
+ it("should return ruff for Python file with pyproject.toml", async () => {
169
+ fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.ruff]\nline-length = 100");
170
+ const pyFile = path.join(TEST_DIR, "test.py");
171
+ const formatters = await getFormattersForFile(pyFile, TEST_DIR);
172
+ expect(formatters.map(f => f.name)).toContain("ruff");
173
+ });
174
+ it("should return black for Python file with black config", async () => {
175
+ fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.black]\nline-length = 100");
176
+ const pyFile = path.join(TEST_DIR, "test.py");
177
+ const formatters = await getFormattersForFile(pyFile, TEST_DIR);
178
+ // Should prefer black over ruff
179
+ expect(formatters.map(f => f.name)).toContain("black");
180
+ });
181
+ it("should return empty array for unsupported extensions", async () => {
182
+ const txtFile = path.join(TEST_DIR, "test.txt");
183
+ fs.writeFileSync(txtFile, "content");
184
+ const formatters = await getFormattersForFile(txtFile, TEST_DIR);
185
+ expect(formatters).toEqual([]);
186
+ });
187
+ it("should cache detection results", async () => {
188
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
189
+ const tsFile = path.join(TEST_DIR, "test.ts");
190
+ // First call
191
+ await getFormattersForFile(tsFile, TEST_DIR);
192
+ // Second call should use cache
193
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
194
+ expect(formatters.map(f => f.name)).toContain("biome");
195
+ });
196
+ });
197
+ describe("clearFormatterCache()", () => {
198
+ it("should clear cached detection results", async () => {
199
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
200
+ const tsFile = path.join(TEST_DIR, "test.ts");
201
+ // First detection
202
+ await getFormattersForFile(tsFile, TEST_DIR);
203
+ // Clear cache
204
+ clearFormatterCache();
205
+ // Delete config
206
+ fs.rmSync(path.join(TEST_DIR, "biome.json"));
207
+ // Should re-detect (now without biome)
208
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
209
+ expect(formatters.map(f => f.name)).not.toContain("biome");
210
+ });
211
+ });
212
+ describe("formatFile()", () => {
213
+ it("should format file and report changes", async () => {
214
+ // Create a simple test - we'll skip actual formatter execution
215
+ // because we can't guarantee formatters are installed
216
+ const testFile = path.join(TEST_DIR, "test.txt");
217
+ fs.writeFileSync(testFile, "unchanged");
218
+ const mockFormatter = {
219
+ name: "mock",
220
+ command: ["echo", "$FILE"],
221
+ extensions: [".txt"],
222
+ detect: async () => true,
223
+ };
224
+ const result = await formatFile(testFile, mockFormatter);
225
+ // echo command should succeed but not change file
226
+ expect(result.success).toBe(true);
227
+ });
228
+ it("should handle formatter execution with valid command", async () => {
229
+ const testFile = path.join(TEST_DIR, "valid.txt");
230
+ fs.writeFileSync(testFile, "content");
231
+ // Use a valid command that succeeds but doesn't modify file
232
+ const mockFormatter = {
233
+ name: "valid",
234
+ command: process.platform === "win32" ? ["cmd", "/c", "echo", "$FILE"] : ["echo", "$FILE"],
235
+ extensions: [".txt"],
236
+ detect: async () => true,
237
+ };
238
+ const result = await formatFile(testFile, mockFormatter);
239
+ // Should not throw, completes with success
240
+ expect(result).toBeDefined();
241
+ expect(typeof result.success).toBe("boolean");
242
+ });
243
+ });
244
+ describe("Formatter extensions", () => {
245
+ it("biome should handle TS/JS/JSON/CSS/Vue/Svelte", () => {
246
+ expect(biomeFormatter.extensions).toContain(".ts");
247
+ expect(biomeFormatter.extensions).toContain(".tsx");
248
+ expect(biomeFormatter.extensions).toContain(".js");
249
+ expect(biomeFormatter.extensions).toContain(".json");
250
+ expect(biomeFormatter.extensions).toContain(".css");
251
+ expect(biomeFormatter.extensions).toContain(".vue");
252
+ expect(biomeFormatter.extensions).toContain(".svelte");
253
+ });
254
+ it("prettier should handle Markdown and YAML", () => {
255
+ expect(prettierFormatter.extensions).toContain(".md");
256
+ expect(prettierFormatter.extensions).toContain(".mdx");
257
+ expect(prettierFormatter.extensions).toContain(".yaml");
258
+ expect(prettierFormatter.extensions).toContain(".yml");
259
+ });
260
+ it("ruff should handle Python files", () => {
261
+ expect(ruffFormatter.extensions).toContain(".py");
262
+ expect(ruffFormatter.extensions).toContain(".pyi");
263
+ });
264
+ it("gofmt should handle Go files", () => {
265
+ expect(gofmtFormatter.extensions).toContain(".go");
266
+ });
267
+ it("rustfmt should handle Rust files", () => {
268
+ expect(rustfmtFormatter.extensions).toContain(".rs");
269
+ });
270
+ });
271
+ });
@@ -0,0 +1,94 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { AgentBehaviorClient } from "./agent-behavior-client.js";
3
+ describe("AgentBehaviorClient", () => {
4
+ let client;
5
+ beforeEach(() => {
6
+ client = new AgentBehaviorClient();
7
+ client.reset();
8
+ });
9
+ describe("blind write detection", () => {
10
+ it("should NOT warn when read precedes write", () => {
11
+ client.recordToolCall("read", "src/file.ts");
12
+ client.recordToolCall("edit", "src/file.ts");
13
+ const warnings = client.recordToolCall("write", "src/file.ts");
14
+ expect(warnings).toHaveLength(0);
15
+ });
16
+ it("should warn when multiple writes happen without reads", () => {
17
+ // First write is OK (no history)
18
+ client.recordToolCall("write", "src/file1.ts");
19
+ // Second write - still in window, accumulates
20
+ client.recordToolCall("edit", "src/file2.ts");
21
+ // Third write without any read - now we have a pattern
22
+ const warnings = client.recordToolCall("edit", "src/file3.ts");
23
+ expect(warnings).toHaveLength(1);
24
+ expect(warnings[0].type).toBe("blind-write");
25
+ });
26
+ it("should not warn for single write with no history", () => {
27
+ const warnings = client.recordToolCall("write", "src/file.ts");
28
+ expect(warnings).toHaveLength(0);
29
+ });
30
+ });
31
+ describe("thrashing detection", () => {
32
+ it("should warn after 3 consecutive identical tool calls", () => {
33
+ client.recordToolCall("bash", undefined);
34
+ // Second call - no warning yet
35
+ let warnings = client.recordToolCall("bash", undefined);
36
+ expect(warnings).toHaveLength(0);
37
+ // Third consecutive - should warn
38
+ warnings = client.recordToolCall("bash", undefined);
39
+ expect(warnings).toHaveLength(1);
40
+ expect(warnings[0].type).toBe("thrashing");
41
+ expect(warnings[0].details.callCount).toBe(3);
42
+ });
43
+ it("should NOT warn for different tool calls", () => {
44
+ client.recordToolCall("read", "src/file.ts");
45
+ client.recordToolCall("bash", "npm test");
46
+ const warnings = client.recordToolCall("edit", "src/file.ts");
47
+ expect(warnings).toHaveLength(0);
48
+ });
49
+ it("should reset count when tool changes", () => {
50
+ client.recordToolCall("bash", undefined);
51
+ client.recordToolCall("bash", undefined);
52
+ // Different tool resets the count
53
+ client.recordToolCall("read", "src/file.ts");
54
+ // Now start new consecutive sequence
55
+ client.recordToolCall("bash", undefined);
56
+ const warnings = client.recordToolCall("bash", undefined);
57
+ expect(warnings).toHaveLength(0); // Only 2 consecutive, not 3
58
+ });
59
+ });
60
+ describe("edit counting", () => {
61
+ it("should track edit count per file", () => {
62
+ client.recordToolCall("edit", "src/a.ts");
63
+ client.recordToolCall("edit", "src/a.ts");
64
+ client.recordToolCall("edit", "src/b.ts");
65
+ expect(client.getEditCount("src/a.ts")).toBe(2);
66
+ expect(client.getEditCount("src/b.ts")).toBe(1);
67
+ expect(client.getEditCount("src/c.ts")).toBe(0);
68
+ });
69
+ });
70
+ describe("formatWarnings", () => {
71
+ it("should format multiple warnings", () => {
72
+ const warnings = [
73
+ {
74
+ type: "blind-write",
75
+ message: "⚠ BLIND WRITE — editing file",
76
+ severity: "warning",
77
+ details: {},
78
+ },
79
+ {
80
+ type: "thrashing",
81
+ message: "🔴 THRASHING — 3 consecutive calls",
82
+ severity: "error",
83
+ details: {},
84
+ },
85
+ ];
86
+ const formatted = client.formatWarnings(warnings);
87
+ expect(formatted).toContain("BLIND WRITE");
88
+ expect(formatted).toContain("THRASHING");
89
+ });
90
+ it("should return empty string for no warnings", () => {
91
+ expect(client.formatWarnings([])).toBe("");
92
+ });
93
+ });
94
+ });
@@ -0,0 +1,144 @@
1
+ import * as fs from "node:fs";
2
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
+ import { BiomeClient } from "./biome-client.js";
4
+ import { createTempFile, setupTestEnvironment } from "./test-utils.js";
5
+ describe("BiomeClient", () => {
6
+ let client;
7
+ let tmpDir;
8
+ let cleanup;
9
+ beforeEach(() => {
10
+ client = new BiomeClient();
11
+ ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-biome-test-"));
12
+ });
13
+ afterEach(() => {
14
+ cleanup();
15
+ });
16
+ afterEach(() => {
17
+ cleanup();
18
+ });
19
+ describe("isSupportedFile", () => {
20
+ it("should support JS/TS files", () => {
21
+ expect(client.isSupportedFile("test.js")).toBe(true);
22
+ expect(client.isSupportedFile("test.jsx")).toBe(true);
23
+ expect(client.isSupportedFile("test.ts")).toBe(true);
24
+ expect(client.isSupportedFile("test.tsx")).toBe(true);
25
+ expect(client.isSupportedFile("test.mjs")).toBe(true);
26
+ expect(client.isSupportedFile("test.cjs")).toBe(true);
27
+ });
28
+ it("should support CSS and JSON", () => {
29
+ expect(client.isSupportedFile("style.css")).toBe(true);
30
+ expect(client.isSupportedFile("config.json")).toBe(true);
31
+ });
32
+ it("should not support unsupported files", () => {
33
+ expect(client.isSupportedFile("test.py")).toBe(false);
34
+ expect(client.isSupportedFile("test.md")).toBe(false);
35
+ expect(client.isSupportedFile("test.txt")).toBe(false);
36
+ });
37
+ });
38
+ describe("isAvailable", () => {
39
+ it("should check biome availability", () => {
40
+ const available = client.isAvailable();
41
+ // Just verify it doesn't throw - actual availability depends on environment
42
+ expect(typeof available).toBe("boolean");
43
+ });
44
+ });
45
+ describe("checkFile", () => {
46
+ it("should return empty array for non-existent files", () => {
47
+ if (!client.isAvailable())
48
+ return;
49
+ const result = client.checkFile("/nonexistent/file.ts");
50
+ expect(result).toEqual([]);
51
+ });
52
+ it("should return array of diagnostics for TS files", {
53
+ timeout: 15000,
54
+ }, () => {
55
+ if (!client.isAvailable())
56
+ return;
57
+ const content = `
58
+ const x: number = "string";
59
+ `;
60
+ const filePath = createTempFile(tmpDir, "test.ts", content);
61
+ const result = client.checkFile(filePath);
62
+ // Should return an array (may or may not have issues)
63
+ expect(Array.isArray(result)).toBe(true);
64
+ });
65
+ });
66
+ describe("formatDiagnostics", () => {
67
+ it("should format diagnostics for display", () => {
68
+ const diags = [
69
+ {
70
+ line: 1,
71
+ column: 0,
72
+ endLine: 1,
73
+ endColumn: 10,
74
+ severity: "error",
75
+ message: "Unexpected var",
76
+ rule: "noVar",
77
+ category: "lint",
78
+ fixable: true,
79
+ },
80
+ ];
81
+ const formatted = client.formatDiagnostics(diags, "test.ts");
82
+ expect(formatted).toContain("Biome");
83
+ expect(formatted).toContain("1 issue");
84
+ expect(formatted).toContain("noVar");
85
+ });
86
+ it("should show fixable count", () => {
87
+ const diags = [
88
+ {
89
+ line: 1,
90
+ column: 0,
91
+ endLine: 1,
92
+ endColumn: 10,
93
+ severity: "error",
94
+ message: "Error 1",
95
+ rule: "rule1",
96
+ category: "lint",
97
+ fixable: true,
98
+ },
99
+ {
100
+ line: 2,
101
+ column: 0,
102
+ endLine: 2,
103
+ endColumn: 10,
104
+ severity: "warning",
105
+ message: "Warning 1",
106
+ rule: "rule2",
107
+ category: "lint",
108
+ fixable: false,
109
+ },
110
+ ];
111
+ const formatted = client.formatDiagnostics(diags, "test.ts");
112
+ expect(formatted).toContain("1 fixable");
113
+ });
114
+ it("should truncate long diagnostic lists", () => {
115
+ const diags = Array.from({ length: 20 }, (_, i) => ({
116
+ line: i + 1,
117
+ column: 0,
118
+ endLine: i + 1,
119
+ endColumn: 10,
120
+ severity: "warning",
121
+ message: `Warning ${i}`,
122
+ rule: `rule${i}`,
123
+ category: "lint",
124
+ fixable: false,
125
+ }));
126
+ const formatted = client.formatDiagnostics(diags, "test.ts");
127
+ expect(formatted).toContain("...");
128
+ expect(formatted).toContain("5 more");
129
+ });
130
+ });
131
+ describe("formatFile", () => {
132
+ it("should format a file", () => {
133
+ if (!client.isAvailable())
134
+ return;
135
+ const content = `const x={a:1,b:2}`;
136
+ const filePath = createTempFile(tmpDir, "test.ts", content);
137
+ const result = client.formatFile(filePath);
138
+ expect(result.success).toBe(true);
139
+ // Check if file was formatted (should have spaces)
140
+ const formatted = fs.readFileSync(filePath, "utf-8");
141
+ expect(formatted).toContain(": "); // Should have spaces after colons
142
+ });
143
+ });
144
+ });