pi-lens 3.1.2 → 3.2.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 (154) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +16 -12
  3. package/clients/ast-grep-client.js +8 -1
  4. package/clients/ast-grep-client.ts +9 -1
  5. package/clients/biome-client.js +51 -38
  6. package/clients/biome-client.ts +60 -58
  7. package/clients/dependency-checker.js +30 -1
  8. package/clients/dependency-checker.ts +35 -1
  9. package/clients/dispatch/__tests__/runner-registration.test.ts +286 -282
  10. package/clients/dispatch/bus-dispatcher.js +15 -14
  11. package/clients/dispatch/bus-dispatcher.ts +32 -25
  12. package/clients/dispatch/dispatcher.js +18 -25
  13. package/clients/dispatch/dispatcher.test.ts +2 -1
  14. package/clients/dispatch/dispatcher.ts +17 -28
  15. package/clients/dispatch/plan.js +77 -32
  16. package/clients/dispatch/plan.ts +78 -32
  17. package/clients/dispatch/runners/ast-grep-napi.js +36 -376
  18. package/clients/dispatch/runners/ast-grep-napi.ts +60 -433
  19. package/clients/dispatch/runners/index.js +8 -4
  20. package/clients/dispatch/runners/index.ts +8 -4
  21. package/clients/dispatch/runners/lsp.js +65 -0
  22. package/clients/dispatch/runners/lsp.ts +125 -0
  23. package/clients/dispatch/runners/oxlint.js +2 -2
  24. package/clients/dispatch/runners/oxlint.ts +2 -2
  25. package/clients/dispatch/runners/pyright.js +24 -8
  26. package/clients/dispatch/runners/pyright.ts +28 -14
  27. package/clients/dispatch/runners/rust-clippy.js +2 -2
  28. package/clients/dispatch/runners/rust-clippy.ts +2 -4
  29. package/clients/dispatch/runners/tree-sitter.js +14 -2
  30. package/clients/dispatch/runners/tree-sitter.ts +15 -2
  31. package/clients/dispatch/runners/ts-lsp.js +3 -3
  32. package/clients/dispatch/runners/ts-lsp.ts +8 -5
  33. package/clients/dispatch/runners/yaml-rule-parser.js +292 -0
  34. package/clients/dispatch/runners/yaml-rule-parser.ts +338 -0
  35. package/clients/dispatch/types.js +3 -0
  36. package/clients/dispatch/types.ts +3 -0
  37. package/clients/formatters.js +67 -14
  38. package/clients/formatters.ts +68 -15
  39. package/clients/installer/index.js +78 -10
  40. package/clients/installer/index.ts +519 -426
  41. package/clients/jscpd-client.js +28 -0
  42. package/clients/jscpd-client.ts +41 -3
  43. package/clients/knip-client.js +30 -1
  44. package/clients/knip-client.ts +34 -2
  45. package/clients/lsp/__tests__/client.test.ts +64 -41
  46. package/clients/lsp/__tests__/config.test.ts +25 -17
  47. package/clients/lsp/__tests__/launch.test.ts +108 -43
  48. package/clients/lsp/__tests__/service.test.ts +76 -48
  49. package/clients/lsp/client.js +87 -2
  50. package/clients/lsp/client.ts +150 -6
  51. package/clients/lsp/config.js +8 -11
  52. package/clients/lsp/config.ts +24 -21
  53. package/clients/lsp/index.js +69 -0
  54. package/clients/lsp/index.ts +82 -0
  55. package/clients/lsp/interactive-install.js +19 -8
  56. package/clients/lsp/interactive-install.ts +52 -27
  57. package/clients/lsp/launch.js +182 -32
  58. package/clients/lsp/launch.ts +241 -38
  59. package/clients/lsp/path-utils.js +3 -46
  60. package/clients/lsp/path-utils.ts +11 -51
  61. package/clients/lsp/server.js +93 -71
  62. package/clients/lsp/server.ts +173 -131
  63. package/clients/path-utils.js +142 -0
  64. package/clients/path-utils.ts +153 -0
  65. package/clients/ruff-client.js +33 -4
  66. package/clients/ruff-client.ts +44 -13
  67. package/clients/safe-spawn.js +3 -1
  68. package/clients/safe-spawn.ts +3 -1
  69. package/clients/services/effect-integration.js +11 -7
  70. package/clients/services/effect-integration.ts +34 -26
  71. package/clients/sg-runner.js +51 -9
  72. package/clients/sg-runner.ts +58 -15
  73. package/clients/tree-sitter-client.js +12 -0
  74. package/clients/tree-sitter-client.ts +12 -0
  75. package/clients/typescript-client.js +6 -2
  76. package/clients/typescript-client.ts +9 -2
  77. package/commands/booboo.js +2 -4
  78. package/commands/booboo.ts +2 -4
  79. package/index.ts +377 -93
  80. package/package.json +2 -1
  81. package/rules/tree-sitter-queries/tsx/no-nested-links.yml +45 -0
  82. package/rules/tree-sitter-queries/typescript/constructor-super.yml +55 -0
  83. package/rules/tree-sitter-queries/typescript/debugger.yml +1 -1
  84. package/rules/tree-sitter-queries/typescript/no-dupe-class-members.yml +47 -0
  85. package/tsconfig.json +1 -1
  86. package/clients/__tests__/file-time.test.js +0 -216
  87. package/clients/__tests__/format-service.test.js +0 -245
  88. package/clients/__tests__/formatters.test.js +0 -271
  89. package/clients/agent-behavior-client.test.js +0 -94
  90. package/clients/ast-grep-client.test.js +0 -129
  91. package/clients/ast-grep-client.test.ts +0 -155
  92. package/clients/biome-client.test.js +0 -144
  93. package/clients/cache-manager.test.js +0 -197
  94. package/clients/complexity-client.test.js +0 -234
  95. package/clients/dependency-checker.test.js +0 -60
  96. package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
  97. package/clients/dispatch/__tests__/runner-registration.test.js +0 -236
  98. package/clients/dispatch/dispatcher.edge.test.js +0 -82
  99. package/clients/dispatch/dispatcher.format.test.js +0 -46
  100. package/clients/dispatch/dispatcher.inline.test.js +0 -74
  101. package/clients/dispatch/dispatcher.test.js +0 -115
  102. package/clients/dispatch/runners/architect.test.js +0 -138
  103. package/clients/dispatch/runners/ast-grep-napi.test.js +0 -106
  104. package/clients/dispatch/runners/oxlint.test.js +0 -230
  105. package/clients/dispatch/runners/pyright.test.js +0 -98
  106. package/clients/dispatch/runners/python-slop.test.js +0 -203
  107. package/clients/dispatch/runners/scan_codebase.test.js +0 -89
  108. package/clients/dispatch/runners/shellcheck.test.js +0 -98
  109. package/clients/dispatch/runners/spellcheck.test.js +0 -158
  110. package/clients/dispatch/runners/ts-slop.test.js +0 -180
  111. package/clients/dispatch/runners/ts-slop.test.ts +0 -230
  112. package/clients/dogfood.test.js +0 -201
  113. package/clients/file-kinds.test.js +0 -169
  114. package/clients/go-client.test.js +0 -127
  115. package/clients/jscpd-client.test.js +0 -127
  116. package/clients/knip-client.test.js +0 -112
  117. package/clients/lsp/__tests__/client.test.js +0 -325
  118. package/clients/lsp/__tests__/config.test.js +0 -166
  119. package/clients/lsp/__tests__/error-recovery.test.js +0 -213
  120. package/clients/lsp/__tests__/integration.test.js +0 -127
  121. package/clients/lsp/__tests__/launch.test.js +0 -260
  122. package/clients/lsp/__tests__/server.test.js +0 -259
  123. package/clients/lsp/__tests__/service.test.js +0 -417
  124. package/clients/metrics-client.test.js +0 -141
  125. package/clients/ruff-client.test.js +0 -132
  126. package/clients/rust-client.test.js +0 -108
  127. package/clients/sanitize.test.js +0 -177
  128. package/clients/secrets-scanner.test.js +0 -100
  129. package/clients/services/__tests__/effect-integration.test.js +0 -86
  130. package/clients/test-runner-client.test.js +0 -192
  131. package/clients/todo-scanner.test.js +0 -301
  132. package/clients/type-coverage-client.test.js +0 -105
  133. package/clients/typescript-client.codefix.test.js +0 -157
  134. package/clients/typescript-client.test.js +0 -105
  135. package/commands/clients/ast-grep-client.js +0 -250
  136. package/commands/clients/ast-grep-parser.js +0 -86
  137. package/commands/clients/ast-grep-rule-manager.js +0 -91
  138. package/commands/clients/ast-grep-types.js +0 -9
  139. package/commands/clients/biome-client.js +0 -380
  140. package/commands/clients/complexity-client.js +0 -667
  141. package/commands/clients/file-kinds.js +0 -177
  142. package/commands/clients/file-utils.js +0 -40
  143. package/commands/clients/jscpd-client.js +0 -169
  144. package/commands/clients/knip-client.js +0 -211
  145. package/commands/clients/ruff-client.js +0 -297
  146. package/commands/clients/safe-spawn.js +0 -88
  147. package/commands/clients/scan-utils.js +0 -83
  148. package/commands/clients/sg-runner.js +0 -190
  149. package/commands/clients/types.js +0 -11
  150. package/commands/clients/typescript-client.js +0 -505
  151. package/commands/rate.test.js +0 -119
  152. package/rules/ast-grep-rules/rules/no-dangerously-set-inner-html.yml +0 -13
  153. package/rules/ast-grep-rules/rules/no-debugger.yml +0 -12
  154. package/rules/ast-grep-rules/rules/no-eval.yml +0 -13
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * LSP Launch Utilities Test Suite
3
- *
3
+ *
4
4
  * Tests for launching LSP servers including:
5
5
  * - Direct binary execution
6
6
  * - Package manager execution (npx/bun)
@@ -9,12 +9,12 @@
9
9
  * - Process cleanup
10
10
  */
11
11
 
12
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
13
- import { spawn, type SpawnOptions, type ChildProcess } from "child_process";
12
+ import { type ChildProcess, spawn } from "node:child_process";
13
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
14
14
  import {
15
15
  launchLSP,
16
- launchViaPackageManager,
17
16
  launchViaNode,
17
+ launchViaPackageManager,
18
18
  launchViaPython,
19
19
  stopLSP,
20
20
  } from "../launch.js";
@@ -27,16 +27,30 @@ vi.mock("child_process", () => ({
27
27
  const mockSpawn = vi.mocked(spawn);
28
28
 
29
29
  // Helper to create mock child process
30
- function createMockChildProcess(pid: number = 123): Partial<ChildProcess> {
31
- return {
30
+ function createMockChildProcess(
31
+ pid: number = 123,
32
+ ): Partial<ChildProcess> & { _emit: (event: string, ...args: any[]) => void } {
33
+ const handlers = new Map<string, Array<(...args: any[]) => void>>();
34
+ const mockObj: any = {
32
35
  pid,
33
36
  stdin: { write: vi.fn() } as any,
34
37
  stdout: { on: vi.fn(), pipe: vi.fn() } as any,
35
38
  stderr: { on: vi.fn(), pipe: vi.fn() } as any,
36
39
  kill: vi.fn(),
37
- on: vi.fn(),
38
- connected: true,
40
+ exitCode: null,
41
+ killed: false,
42
+ on(event: string, handler: (...args: any[]) => void) {
43
+ if (!handlers.has(event)) {
44
+ handlers.set(event, []);
45
+ }
46
+ handlers.get(event)?.push(handler);
47
+ return mockObj;
48
+ },
49
+ _emit(event: string, ...args: any[]) {
50
+ handlers.get(event)?.forEach((h) => h(...args));
51
+ },
39
52
  };
53
+ return mockObj;
40
54
  }
41
55
 
42
56
  describe("launchLSP", () => {
@@ -48,11 +62,20 @@ describe("launchLSP", () => {
48
62
  const mockProcess = createMockChildProcess();
49
63
  mockSpawn.mockReturnValue(mockProcess as ChildProcess);
50
64
 
51
- launchLSP("typescript-language-server", ["--stdio"], { cwd: "/test" });
65
+ // Start the async operation
66
+ const launchPromise = launchLSP("typescript-language-server", ["--stdio"], {
67
+ cwd: "/test",
68
+ });
69
+
70
+ // Let the 50ms timeout pass
71
+ await new Promise((r) => setTimeout(r, 60));
72
+
73
+ // Now complete by resolving
74
+ const _result = await launchPromise;
52
75
 
53
76
  expect(mockSpawn).toHaveBeenCalled();
54
- const [cmd, args, options] = mockSpawn.mock.calls[0];
55
-
77
+ const [cmd, _args, options] = mockSpawn.mock.calls[0];
78
+
56
79
  // Command should be provided (format depends on platform)
57
80
  expect(cmd).toBeTruthy();
58
81
  expect(options).toMatchObject({
@@ -68,7 +91,10 @@ describe("launchLSP", () => {
68
91
  mockSpawn.mockReturnValue(mockProcess as ChildProcess);
69
92
 
70
93
  const customEnv = { CUSTOM_VAR: "value" };
71
- launchLSP("server", [], { env: customEnv });
94
+ const launchPromise = launchLSP("server", [], { env: customEnv });
95
+
96
+ await new Promise((r) => setTimeout(r, 60));
97
+ await launchPromise;
72
98
 
73
99
  const [, , options] = mockSpawn.mock.calls[0];
74
100
  expect(options?.env).toMatchObject({
@@ -80,7 +106,9 @@ describe("launchLSP", () => {
80
106
  const mockProcess = createMockChildProcess(456);
81
107
  mockSpawn.mockReturnValue(mockProcess as ChildProcess);
82
108
 
83
- const result = launchLSP("server", [], {});
109
+ const launchPromise = launchLSP("server", [], {});
110
+ await new Promise((r) => setTimeout(r, 60));
111
+ const result = await launchPromise;
84
112
 
85
113
  expect(result.pid).toBe(456);
86
114
  expect(result.stdin).toBeDefined();
@@ -89,31 +117,55 @@ describe("launchLSP", () => {
89
117
  });
90
118
 
91
119
  it("should throw if stdin is not available", async () => {
92
- const mockProcess = { ...createMockChildProcess(), stdin: null };
120
+ const baseMock = createMockChildProcess();
121
+ const mockProcess = {
122
+ ...baseMock,
123
+ stdin: null,
124
+ on: baseMock.on,
125
+ _emit: baseMock._emit,
126
+ };
93
127
  mockSpawn.mockReturnValue(mockProcess as ChildProcess);
94
128
 
95
- expect(() => launchLSP("server", [], {})).toThrow("Failed to spawn LSP server");
129
+ // Use wrapper function for proper async error catching
130
+ const launchFn = async () => launchLSP("server", [], {});
131
+ await expect(launchFn()).rejects.toThrow("Failed to spawn LSP server");
96
132
  });
97
133
 
98
134
  it("should throw if stdout is not available", async () => {
99
- const mockProcess = { ...createMockChildProcess(), stdout: null };
135
+ const baseMock = createMockChildProcess();
136
+ const mockProcess = {
137
+ ...baseMock,
138
+ stdout: null,
139
+ on: baseMock.on,
140
+ _emit: baseMock._emit,
141
+ };
100
142
  mockSpawn.mockReturnValue(mockProcess as ChildProcess);
101
143
 
102
- expect(() => launchLSP("server", [], {})).toThrow("Failed to spawn LSP server");
144
+ const launchFn = async () => launchLSP("server", [], {});
145
+ await expect(launchFn()).rejects.toThrow("Failed to spawn LSP server");
103
146
  });
104
147
 
105
148
  it("should throw if stderr is not available", async () => {
106
- const mockProcess = { ...createMockChildProcess(), stderr: null };
149
+ const baseMock = createMockChildProcess();
150
+ const mockProcess = {
151
+ ...baseMock,
152
+ stderr: null,
153
+ on: baseMock.on,
154
+ _emit: baseMock._emit,
155
+ };
107
156
  mockSpawn.mockReturnValue(mockProcess as ChildProcess);
108
157
 
109
- expect(() => launchLSP("server", [], {})).toThrow("Failed to spawn LSP server");
158
+ const launchFn = async () => launchLSP("server", [], {});
159
+ await expect(launchFn()).rejects.toThrow("Failed to spawn LSP server");
110
160
  });
111
161
 
112
162
  it("should default to process.cwd() when cwd not provided", async () => {
113
163
  const mockProcess = createMockChildProcess();
114
164
  mockSpawn.mockReturnValue(mockProcess as ChildProcess);
115
165
 
116
- launchLSP("server", [], {});
166
+ const launchPromise = launchLSP("server", [], {});
167
+ await new Promise((r) => setTimeout(r, 60));
168
+ await launchPromise;
117
169
 
118
170
  const [, , options] = mockSpawn.mock.calls[0];
119
171
  expect(options?.cwd).toBe(process.cwd());
@@ -124,11 +176,16 @@ describe("launchLSP", () => {
124
176
  mockSpawn.mockReturnValue(mockProcess as ChildProcess);
125
177
 
126
178
  // Save original platform
127
- const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
179
+ const originalPlatform = Object.getOwnPropertyDescriptor(
180
+ process,
181
+ "platform",
182
+ );
128
183
  Object.defineProperty(process, "platform", { value: "win32" });
129
184
 
130
185
  try {
131
- launchLSP("server.cmd", ["--arg"], {});
186
+ const launchPromise = launchLSP("server.cmd", ["--arg"], {});
187
+ await new Promise((r) => setTimeout(r, 60));
188
+ await launchPromise;
132
189
 
133
190
  const [, , options] = mockSpawn.mock.calls[0];
134
191
  expect(options?.shell).toBe(true);
@@ -171,8 +228,9 @@ describe("launchViaPackageManager", () => {
171
228
  const [cmd, args] = mockSpawn.mock.calls[0];
172
229
  // On Windows with shell mode, args may be concatenated into cmd
173
230
  const cmdStr = String(cmd);
174
- const hasBun = cmdStr.includes("bun") || (args && args.some((a: string) => a && a.includes("bun")));
175
- const hasX = cmdStr.includes(" x ") || (args && args.includes("x"));
231
+ const hasBun =
232
+ cmdStr.includes("bun") || args?.some((a: string) => a?.includes("bun"));
233
+ const _hasX = cmdStr.includes(" x ") || args?.includes("x");
176
234
  expect(hasBun).toBe(true);
177
235
  // Note: 'x' arg may be in command string on Windows
178
236
  });
@@ -200,8 +258,8 @@ describe("launchViaNode", () => {
200
258
  launchViaNode("/path/to/script.js", ["--arg"], { cwd: "/test" });
201
259
 
202
260
  expect(mockSpawn).toHaveBeenCalled();
203
- const [cmd, args, options] = mockSpawn.mock.calls[0];
204
-
261
+ const [cmd, _args, options] = mockSpawn.mock.calls[0];
262
+
205
263
  // On Windows, command is combined; on Unix, it's separate
206
264
  expect(String(cmd)).toContain("node");
207
265
  expect(options?.cwd).toBe("/test");
@@ -228,14 +286,19 @@ describe("launchViaPython", () => {
228
286
  const mockProcess = createMockChildProcess();
229
287
  mockSpawn.mockReturnValue(mockProcess as ChildProcess);
230
288
 
231
- launchViaPython("pylsp", ["--verbose", "--log-file", "/tmp/log"], {});
289
+ await launchViaPython("pylsp", ["--verbose", "--log-file", "/tmp/log"], {});
232
290
 
233
291
  expect(mockSpawn).toHaveBeenCalled();
234
292
  const [cmd, args] = mockSpawn.mock.calls[0];
235
- // Args should include the module and the passed args
236
- expect(args).toContain("-m");
237
- expect(args).toContain("pylsp");
238
- expect(args).toContain("--verbose");
293
+ // On Windows with shell mode, args may be combined into cmd string
294
+ // Check that the command contains all expected parts
295
+ const fullCommand =
296
+ typeof cmd === "string" && Array.isArray(args) && args.length === 0
297
+ ? cmd // shell mode: everything in cmd
298
+ : `${cmd} ${args.join(" ")}`; // normal mode
299
+ expect(fullCommand).toContain("-m");
300
+ expect(fullCommand).toContain("pylsp");
301
+ expect(fullCommand).toContain("--verbose");
239
302
  });
240
303
  });
241
304
 
@@ -252,7 +315,7 @@ describe("stopLSP", () => {
252
315
  it("should send SIGTERM first", async () => {
253
316
  const mockKill = vi.fn();
254
317
  const exitHandlers: Array<() => void> = [];
255
-
318
+
256
319
  const mockProcess = {
257
320
  ...createMockChildProcess(),
258
321
  kill: mockKill,
@@ -265,7 +328,7 @@ describe("stopLSP", () => {
265
328
  }),
266
329
  };
267
330
 
268
- const stopPromise = stopLSP({
331
+ const _stopPromise = stopLSP({
269
332
  process: mockProcess as any,
270
333
  stdin: {} as any,
271
334
  stdout: {} as any,
@@ -274,8 +337,8 @@ describe("stopLSP", () => {
274
337
  });
275
338
 
276
339
  // Let the exit handler fire
277
- await new Promise(r => setTimeout(r, 20));
278
-
340
+ await new Promise((r) => setTimeout(r, 20));
341
+
279
342
  expect(mockKill).toHaveBeenCalledWith("SIGTERM");
280
343
  });
281
344
 
@@ -295,13 +358,15 @@ describe("stopLSP", () => {
295
358
  }),
296
359
  };
297
360
 
298
- await expect(stopLSP({
299
- process: mockProcess as any,
300
- stdin: {} as any,
301
- stdout: {} as any,
302
- stderr: {} as any,
303
- pid: 123,
304
- })).resolves.toBeUndefined();
361
+ await expect(
362
+ stopLSP({
363
+ process: mockProcess as any,
364
+ stdin: {} as any,
365
+ stdout: {} as any,
366
+ stderr: {} as any,
367
+ pid: 123,
368
+ }),
369
+ ).resolves.toBeUndefined();
305
370
  });
306
371
 
307
372
  it("should resolve on error event", async () => {
@@ -323,7 +388,7 @@ describe("stopLSP", () => {
323
388
  });
324
389
 
325
390
  // Let the error handler fire
326
- await new Promise(r => setTimeout(r, 20));
391
+ await new Promise((r) => setTimeout(r, 20));
327
392
  await expect(stopPromise).resolves.toBeUndefined();
328
393
  });
329
394
  });
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * LSP Service Test Suite
3
- *
3
+ *
4
4
  * Tests for the LSP Service layer including:
5
5
  * - Client lifecycle management
6
6
  * - Effect-TS integration
@@ -9,15 +9,21 @@
9
9
  * - Cleanup and shutdown
10
10
  */
11
11
 
12
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
13
12
  import { Effect } from "effect";
14
- import { LSPService, lspEffect, getLSPService, resetLSPService } from "../index.js";
13
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
15
14
  import type { LSPClientInfo } from "../client.js";
15
+ import {
16
+ getLSPService,
17
+ LSPService,
18
+ lspEffect,
19
+ resetLSPService,
20
+ } from "../index.js";
16
21
  import type { LSPServerInfo } from "../server.js";
17
22
 
18
23
  // Mock the dependencies
19
24
  vi.mock("../server.js", async () => {
20
- const actual = await vi.importActual<typeof import("../server.js")>("../server.js");
25
+ const actual =
26
+ await vi.importActual<typeof import("../server.js")>("../server.js");
21
27
  return {
22
28
  ...actual,
23
29
  getServersForFileWithConfig: vi.fn(),
@@ -41,8 +47,8 @@ vi.mock("../client.js", () => ({
41
47
  createLSPClient: vi.fn(),
42
48
  }));
43
49
 
44
- import { getServersForFileWithConfig as mockGetServersForFile } from "../config.js";
45
50
  import { createLSPClient as mockCreateLSPClient } from "../client.js";
51
+ import { getServersForFileWithConfig as mockGetServersForFile } from "../config.js";
46
52
 
47
53
  describe("LSPService", () => {
48
54
  let service: LSPService;
@@ -74,7 +80,7 @@ describe("LSPService", () => {
74
80
  describe("hasLSP", () => {
75
81
  it("should return false when no servers match file extension", async () => {
76
82
  vi.mocked(mockGetServersForFile).mockReturnValue([]);
77
-
83
+
78
84
  const result = await service.hasLSP("/test/file.unknown");
79
85
  expect(result).toBe(false);
80
86
  });
@@ -88,7 +94,7 @@ describe("LSPService", () => {
88
94
  spawn: vi.fn(),
89
95
  };
90
96
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
91
-
97
+
92
98
  const result = await service.hasLSP("/project/test.ts");
93
99
  expect(result).toBe(true);
94
100
  });
@@ -102,7 +108,7 @@ describe("LSPService", () => {
102
108
  spawn: vi.fn(),
103
109
  };
104
110
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
105
-
111
+
106
112
  const result = await service.hasLSP("/project/test.ts");
107
113
  expect(result).toBe(false);
108
114
  });
@@ -123,7 +129,7 @@ describe("LSPService", () => {
123
129
  spawn: vi.fn(),
124
130
  };
125
131
  vi.mocked(mockGetServersForFile).mockReturnValue([server1, server2]);
126
-
132
+
127
133
  const result = await service.hasLSP("/project/test.ts");
128
134
  expect(result).toBe(true);
129
135
  expect(server1.root).toHaveBeenCalled();
@@ -134,7 +140,7 @@ describe("LSPService", () => {
134
140
  describe("getClientForFile", () => {
135
141
  it("should return undefined when no servers match", async () => {
136
142
  vi.mocked(mockGetServersForFile).mockReturnValue([]);
137
-
143
+
138
144
  const result = await service.getClientForFile("/test/file.unknown");
139
145
  expect(result).toBeUndefined();
140
146
  });
@@ -152,12 +158,12 @@ describe("LSPService", () => {
152
158
  };
153
159
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
154
160
  vi.mocked(mockCreateLSPClient).mockResolvedValue(mockClient as any);
155
-
161
+
156
162
  // First call creates client
157
163
  const result1 = await service.getClientForFile("/project/test.ts");
158
164
  expect(result1).toBeDefined();
159
165
  expect(mockCreateLSPClient).toHaveBeenCalledTimes(1);
160
-
166
+
161
167
  // Second call returns cached client
162
168
  const result2 = await service.getClientForFile("/project/other.ts");
163
169
  expect(result2?.client).toBe(result1?.client);
@@ -174,7 +180,7 @@ describe("LSPService", () => {
174
180
  spawn: mockSpawn as any,
175
181
  };
176
182
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
177
-
183
+
178
184
  const result = await service.getClientForFile("/project/test.ts");
179
185
  expect(result).toBeUndefined();
180
186
  });
@@ -197,9 +203,12 @@ describe("LSPService", () => {
197
203
  process: { pid: 456 } as any,
198
204
  }),
199
205
  };
200
- vi.mocked(mockGetServersForFile).mockReturnValue([failingServer, workingServer]);
206
+ vi.mocked(mockGetServersForFile).mockReturnValue([
207
+ failingServer,
208
+ workingServer,
209
+ ]);
201
210
  vi.mocked(mockCreateLSPClient).mockResolvedValue(mockClient as any);
202
-
211
+
203
212
  const result = await service.getClientForFile("/project/test.ts");
204
213
  expect(result).toBeDefined();
205
214
  expect(result?.info.id).toBe("server2");
@@ -220,22 +229,23 @@ describe("LSPService", () => {
220
229
  };
221
230
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
222
231
  vi.mocked(mockCreateLSPClient).mockResolvedValue(mockClient as any);
223
-
232
+
224
233
  await service.openFile("/project/test.ts", "const x = 1;");
225
-
234
+
226
235
  expect(mockClient.notify.open).toHaveBeenCalledWith(
227
236
  "/project/test.ts",
228
237
  "const x = 1;",
229
- "typescript"
238
+ "typescript",
230
239
  );
231
240
  });
232
241
 
233
242
  it("should do nothing when no LSP available", async () => {
234
243
  vi.mocked(mockGetServersForFile).mockReturnValue([]);
235
-
244
+
236
245
  // Should not throw
237
- await expect(service.openFile("/test.unknown", "content"))
238
- .resolves.not.toThrow();
246
+ await expect(
247
+ service.openFile("/test.unknown", "content"),
248
+ ).resolves.not.toThrow();
239
249
  });
240
250
  });
241
251
 
@@ -253,12 +263,12 @@ describe("LSPService", () => {
253
263
  };
254
264
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
255
265
  vi.mocked(mockCreateLSPClient).mockResolvedValue(mockClient as any);
256
-
266
+
257
267
  await service.updateFile("/project/test.ts", "const x = 2;");
258
-
268
+
259
269
  expect(mockClient.notify.change).toHaveBeenCalledWith(
260
270
  "/project/test.ts",
261
- "const x = 2;"
271
+ "const x = 2;",
262
272
  );
263
273
  });
264
274
  });
@@ -287,16 +297,16 @@ describe("LSPService", () => {
287
297
  };
288
298
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
289
299
  vi.mocked(mockCreateLSPClient).mockResolvedValue(mockClient as any);
290
-
300
+
291
301
  const result = await service.getDiagnostics("/project/test.ts");
292
-
302
+
293
303
  expect(result).toEqual(mockDiagnostics);
294
304
  expect(mockClient.waitForDiagnostics).toHaveBeenCalled();
295
305
  });
296
306
 
297
307
  it("should return empty array when no LSP available", async () => {
298
308
  vi.mocked(mockGetServersForFile).mockReturnValue([]);
299
-
309
+
300
310
  const result = await service.getDiagnostics("/test.unknown");
301
311
  expect(result).toEqual([]);
302
312
  });
@@ -306,7 +316,7 @@ describe("LSPService", () => {
306
316
  it("should shutdown all clients", async () => {
307
317
  const mockClient1 = createMockClient();
308
318
  const mockClient2 = createMockClient();
309
-
319
+
310
320
  // Add clients to service
311
321
  const mockServer1: LSPServerInfo = {
312
322
  id: "typescript",
@@ -322,29 +332,31 @@ describe("LSPService", () => {
322
332
  root: vi.fn().mockResolvedValue("/project2"),
323
333
  spawn: vi.fn().mockResolvedValue({ process: { pid: 2 } as any }),
324
334
  };
325
-
335
+
326
336
  vi.mocked(mockGetServersForFile)
327
337
  .mockReturnValueOnce([mockServer1])
328
338
  .mockReturnValueOnce([mockServer2]);
329
-
339
+
330
340
  vi.mocked(mockCreateLSPClient)
331
341
  .mockResolvedValueOnce(mockClient1)
332
342
  .mockResolvedValueOnce(mockClient2);
333
-
343
+
334
344
  // Create clients
335
345
  await service.getClientForFile("/project1/test.ts");
336
346
  await service.getClientForFile("/project2/test.py");
337
-
347
+
338
348
  await service.shutdown();
339
-
349
+
340
350
  expect(mockClient1.shutdown).toHaveBeenCalled();
341
351
  expect(mockClient2.shutdown).toHaveBeenCalled();
342
352
  });
343
353
 
344
354
  it("should handle shutdown errors gracefully", async () => {
345
355
  const mockClient = createMockClient();
346
- (mockClient.shutdown as any).mockRejectedValue(new Error("Shutdown failed"));
347
-
356
+ (mockClient.shutdown as any).mockRejectedValue(
357
+ new Error("Shutdown failed"),
358
+ );
359
+
348
360
  const mockServer: LSPServerInfo = {
349
361
  id: "typescript",
350
362
  name: "TypeScript Server",
@@ -354,9 +366,9 @@ describe("LSPService", () => {
354
366
  };
355
367
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
356
368
  vi.mocked(mockCreateLSPClient).mockResolvedValue(mockClient as any);
357
-
369
+
358
370
  await service.getClientForFile("/project/test.ts");
359
-
371
+
360
372
  // Should not throw even when client shutdown fails
361
373
  await expect(service.shutdown()).resolves.not.toThrow();
362
374
  });
@@ -374,9 +386,9 @@ describe("LSPService", () => {
374
386
  };
375
387
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
376
388
  vi.mocked(mockCreateLSPClient).mockResolvedValue(mockClient as any);
377
-
389
+
378
390
  await service.getClientForFile("/project/test.ts");
379
-
391
+
380
392
  const status = service.getStatus();
381
393
  expect(status).toHaveLength(1);
382
394
  expect(status[0]).toEqual({
@@ -416,17 +428,17 @@ describe("lspEffect", () => {
416
428
  };
417
429
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
418
430
  vi.mocked(mockCreateLSPClient).mockResolvedValue(mockClient as any);
419
-
431
+
420
432
  const program = effect.openFile("/project/test.ts", "content");
421
433
  const result = await Effect.runPromise(program);
422
-
434
+
423
435
  expect(result).toBeUndefined();
424
436
  expect(mockClient.notify.open).toHaveBeenCalled();
425
437
  });
426
438
 
427
439
  it("should handle errors in Effect", async () => {
428
440
  vi.mocked(mockGetServersForFile).mockReturnValue([]);
429
-
441
+
430
442
  const program = effect.openFile("/test.unknown", "content");
431
443
  // Should complete successfully (no-op for unknown files)
432
444
  await expect(Effect.runPromise(program)).resolves.not.toThrow();
@@ -435,7 +447,16 @@ describe("lspEffect", () => {
435
447
 
436
448
  describe("getDiagnostics Effect", () => {
437
449
  it("should wrap getDiagnostics in Effect", async () => {
438
- const mockDiagnostics = [{ severity: 1, message: "Error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } } }];
450
+ const mockDiagnostics = [
451
+ {
452
+ severity: 1,
453
+ message: "Error",
454
+ range: {
455
+ start: { line: 0, character: 0 },
456
+ end: { line: 0, character: 1 },
457
+ },
458
+ },
459
+ ];
439
460
  const mockClient = createMockClient(mockDiagnostics);
440
461
  const mockServer: LSPServerInfo = {
441
462
  id: "typescript",
@@ -446,10 +467,10 @@ describe("lspEffect", () => {
446
467
  };
447
468
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
448
469
  vi.mocked(mockCreateLSPClient).mockResolvedValue(mockClient as any);
449
-
470
+
450
471
  const program = effect.getDiagnostics("/project/test.ts");
451
472
  const result = await Effect.runPromise(program);
452
-
473
+
453
474
  expect(result).toEqual(mockDiagnostics);
454
475
  });
455
476
  });
@@ -464,10 +485,10 @@ describe("lspEffect", () => {
464
485
  spawn: vi.fn(),
465
486
  };
466
487
  vi.mocked(mockGetServersForFile).mockReturnValue([mockServer]);
467
-
488
+
468
489
  const program = effect.hasLSP("/project/test.ts");
469
490
  const result = await Effect.runPromise(program);
470
-
491
+
471
492
  expect(result).toBe(true);
472
493
  });
473
494
  });
@@ -476,7 +497,7 @@ describe("lspEffect", () => {
476
497
  it("should wrap shutdown in Effect", async () => {
477
498
  const program = effect.shutdown();
478
499
  const result = await Effect.runPromise(program);
479
-
500
+
480
501
  expect(result).toBeUndefined();
481
502
  });
482
503
  });
@@ -493,7 +514,14 @@ function createMockClient(diagnostics: any[] = []): LSPClientInfo {
493
514
  change: vi.fn().mockResolvedValue(undefined),
494
515
  },
495
516
  getDiagnostics: vi.fn().mockReturnValue(diagnostics),
517
+ getAllDiagnostics: vi.fn().mockReturnValue(new Map()),
496
518
  waitForDiagnostics: vi.fn().mockResolvedValue(undefined),
519
+ definition: vi.fn().mockResolvedValue([]),
520
+ references: vi.fn().mockResolvedValue([]),
521
+ hover: vi.fn().mockResolvedValue(null),
522
+ documentSymbol: vi.fn().mockResolvedValue([]),
523
+ workspaceSymbol: vi.fn().mockResolvedValue([]),
524
+ implementation: vi.fn().mockResolvedValue([]),
497
525
  shutdown: vi.fn().mockResolvedValue(undefined),
498
526
  };
499
527
  }