Skip to content

Commit 63d960b

Browse files
committed
fix(lsp): improve error handling and process lifecycle management
- Fix empty catch blocks in nearestRoot() to check for ENOENT specifically - Fix empty catch block in DenoServer.root() for consistent error handling - Add attachLSPProcessHandlers to TypeScript, Deno, Pyright, Gopls, RustAnalyzer, and Oxlint servers for better process lifecycle logging - Add error handling for bunx fallback in TypeScript and Pyright servers - Fix misleading test name "returns projectPath when no schema found" to "detects schema.prisma at project root"
1 parent ab0942a commit 63d960b

2 files changed

Lines changed: 65 additions & 28 deletions

File tree

packages/lsp/src/server.ts

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,14 @@ function nearestRoot(
180180
await fs.access(target)
181181
return undefined // Excluded
182182
}
183-
catch {
184-
// Not found, continue
183+
catch (err) {
184+
// Only continue searching if file not found
185+
const isNotFound = err instanceof Error
186+
&& 'code' in err
187+
&& (err as NodeJS.ErrnoException).code === 'ENOENT'
188+
if (!isNotFound) {
189+
console.warn(`[lsp] Unexpected error accessing ${target}:`, err instanceof Error ? err.message : err)
190+
}
185191
}
186192
}
187193
const parent = path.dirname(checkDir)
@@ -199,8 +205,14 @@ function nearestRoot(
199205
await fs.access(target)
200206
return current
201207
}
202-
catch {
203-
// Not found, continue
208+
catch (err) {
209+
// Only continue searching if file not found
210+
const isNotFound = err instanceof Error
211+
&& 'code' in err
212+
&& (err as NodeJS.ErrnoException).code === 'ENOENT'
213+
if (!isNotFound) {
214+
console.warn(`[lsp] Unexpected error accessing ${target}:`, err instanceof Error ? err.message : err)
215+
}
204216
}
205217
}
206218
const parent = path.dirname(current)
@@ -233,17 +245,25 @@ export const TypescriptServer: LSPServerInfo = {
233245
const tsserver = Bun.which('typescript-language-server')
234246
if (!tsserver) {
235247
// Try via bunx
236-
const proc = spawn('bunx', ['typescript-language-server', '--stdio'], {
237-
cwd: root,
238-
env: { ...process.env, BUN_BE_BUN: '1' },
239-
})
240-
return { process: proc }
248+
try {
249+
const proc = spawn('bunx', ['typescript-language-server', '--stdio'], {
250+
cwd: root,
251+
env: { ...process.env, BUN_BE_BUN: '1' },
252+
})
253+
attachLSPProcessHandlers(proc, 'typescript')
254+
return { process: proc }
255+
}
256+
catch (err) {
257+
console.error('[typescript] Failed to spawn via bunx. Install typescript-language-server globally or ensure bunx is available:', err)
258+
return undefined
259+
}
241260
}
242261

243262
const proc = spawn(tsserver, ['--stdio'], {
244263
cwd: root,
245264
env: { ...process.env, BUN_BE_BUN: '1' },
246265
})
266+
attachLSPProcessHandlers(proc, 'typescript')
247267
return { process: proc }
248268
},
249269
}
@@ -263,8 +283,14 @@ export const DenoServer: LSPServerInfo = {
263283
await fs.access(target)
264284
return current
265285
}
266-
catch {
267-
// Not found
286+
catch (err) {
287+
// Only continue searching if file not found
288+
const isNotFound = err instanceof Error
289+
&& 'code' in err
290+
&& (err as NodeJS.ErrnoException).code === 'ENOENT'
291+
if (!isNotFound) {
292+
console.warn(`[deno] Unexpected error accessing ${target}:`, err instanceof Error ? err.message : err)
293+
}
268294
}
269295
}
270296
const parent = path.dirname(current)
@@ -282,6 +308,7 @@ export const DenoServer: LSPServerInfo = {
282308
const proc = spawn(deno, ['lsp'], {
283309
cwd: root,
284310
})
311+
attachLSPProcessHandlers(proc, 'deno')
285312
return { process: proc }
286313
},
287314
}
@@ -302,15 +329,23 @@ export const PyrightServer: LSPServerInfo = {
302329
const pyright = Bun.which('pyright-langserver')
303330
if (!pyright) {
304331
// Try via bunx/npx
305-
const proc = spawn('bunx', ['pyright-langserver', '--stdio'], {
306-
cwd: root,
307-
})
308-
return { process: proc }
332+
try {
333+
const proc = spawn('bunx', ['pyright-langserver', '--stdio'], {
334+
cwd: root,
335+
})
336+
attachLSPProcessHandlers(proc, 'pyright')
337+
return { process: proc }
338+
}
339+
catch (err) {
340+
console.error('[pyright] Failed to spawn via bunx. Install pyright-langserver globally or ensure bunx is available:', err)
341+
return undefined
342+
}
309343
}
310344

311345
const proc = spawn(pyright, ['--stdio'], {
312346
cwd: root,
313347
})
348+
attachLSPProcessHandlers(proc, 'pyright')
314349
return { process: proc }
315350
},
316351
}
@@ -336,6 +371,7 @@ export const GoplsServer: LSPServerInfo = {
336371
const proc = spawn(gopls, ['serve'], {
337372
cwd: root,
338373
})
374+
attachLSPProcessHandlers(proc, 'gopls')
339375
return { process: proc }
340376
},
341377
}
@@ -355,6 +391,7 @@ export const RustAnalyzerServer: LSPServerInfo = {
355391
const proc = spawn(rustAnalyzer, [], {
356392
cwd: root,
357393
})
394+
attachLSPProcessHandlers(proc, 'rust-analyzer')
358395
return { process: proc }
359396
},
360397
}
@@ -435,23 +472,23 @@ export const OxlintServer: LSPServerInfo = {
435472
else {
436473
const help = await new Response(proc.stdout).text()
437474
if (help.includes('--lsp')) {
438-
return {
439-
process: spawn(lintBin, ['--lsp'], {
440-
cwd: root,
441-
}),
442-
}
475+
const lspProc = spawn(lintBin, ['--lsp'], {
476+
cwd: root,
477+
})
478+
attachLSPProcessHandlers(lspProc, 'oxlint')
479+
return { process: lspProc }
443480
}
444481
}
445482
}
446483

447484
// Fallback to oxc_language_server
448485
const serverBin = await resolveBin('oxc_language_server')
449486
if (serverBin) {
450-
return {
451-
process: spawn(serverBin, [], {
452-
cwd: root,
453-
}),
454-
}
487+
const serverProc = spawn(serverBin, [], {
488+
cwd: root,
489+
})
490+
attachLSPProcessHandlers(serverProc, 'oxlint')
491+
return { process: serverProc }
455492
}
456493

457494
// Neither found - log diagnostic

packages/lsp/test/unit/server.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -542,10 +542,10 @@ describe('PrismaServer', () => {
542542
}
543543
})
544544

545-
test('root function returns projectPath when no schema found', async () => {
546-
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'prisma-no-schema-'))
545+
test('root function detects schema.prisma at project root', async () => {
546+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'prisma-root-schema-'))
547547
try {
548-
// No schema.prisma files
548+
// schema.prisma at project root (not in prisma/ subdirectory)
549549
const prismaFile = path.join(tempDir, 'schema.prisma')
550550
await fs.writeFile(prismaFile, '// test')
551551

0 commit comments

Comments
 (0)