From 03da2e94a24372ce33800e966c0aad4dca4ea92e Mon Sep 17 00:00:00 2001 From: acamq <179265037+acamq@users.noreply.github.com> Date: Sun, 15 Mar 2026 18:39:56 -0600 Subject: [PATCH] refactor(doctor): show detected LSP servers and extensions instead of hardcoded counts Replace the hardcoded 4-server list in doctor LSP check with getAllServers() from server-resolution.ts, which covers all 40+ builtin servers plus user config. Output now shows server count with supported extensions, and verbose mode expands to per-server detail lines. Status: LSP 3 servers (.go, .py, .pyi, .ts, .tsx) Verbose: LSP 3 servers (.go, .py, .pyi, .ts, .tsx) typescript (.ts, .tsx, .js, .jsx) pyright (.py, .pyi) gopls (.go) Closes #2587 --- src/cli/doctor/checks/tools-lsp.ts | 28 ++++++--------------------- src/cli/doctor/checks/tools.ts | 12 +++++------- src/cli/doctor/format-default.test.ts | 3 +-- src/cli/doctor/format-status.ts | 6 ++++-- src/cli/doctor/format-verbose.ts | 10 +++++++++- src/cli/doctor/formatter.test.ts | 8 +++++--- src/cli/doctor/runner.test.ts | 3 +-- src/cli/doctor/types.ts | 3 +-- 8 files changed, 32 insertions(+), 41 deletions(-) diff --git a/src/cli/doctor/checks/tools-lsp.ts b/src/cli/doctor/checks/tools-lsp.ts index 900a93e70..945621367 100644 --- a/src/cli/doctor/checks/tools-lsp.ts +++ b/src/cli/doctor/checks/tools-lsp.ts @@ -1,25 +1,9 @@ -import type { LspServerInfo } from "../types" -import { isServerInstalled } from "../../../tools/lsp/config" +import { getAllServers } from "../../../tools/lsp/config" -const DEFAULT_LSP_SERVERS: Array<{ id: string; binary: string; extensions: string[] }> = [ - { id: "typescript-language-server", binary: "typescript-language-server", extensions: [".ts", ".tsx", ".js", ".jsx"] }, - { id: "pyright", binary: "pyright-langserver", extensions: [".py"] }, - { id: "rust-analyzer", binary: "rust-analyzer", extensions: [".rs"] }, - { id: "gopls", binary: "gopls", extensions: [".go"] }, -] +export function getInstalledLspServers(): Array<{ id: string; extensions: string[] }> { + const servers = getAllServers() -export function getLspServersInfo(): LspServerInfo[] { - return DEFAULT_LSP_SERVERS.map((server) => ({ - id: server.id, - installed: isServerInstalled([server.binary]), - extensions: server.extensions, - source: "builtin", - })) -} - -export function getLspServerStats(servers: LspServerInfo[]): { installed: number; total: number } { - return { - installed: servers.filter((server) => server.installed).length, - total: servers.length, - } + return servers + .filter((s) => s.installed && !s.disabled) + .map((s) => ({ id: s.id, extensions: s.extensions })) } diff --git a/src/cli/doctor/checks/tools.ts b/src/cli/doctor/checks/tools.ts index a182a063c..8f635d69f 100644 --- a/src/cli/doctor/checks/tools.ts +++ b/src/cli/doctor/checks/tools.ts @@ -1,6 +1,6 @@ import { checkAstGrepCli, checkAstGrepNapi, checkCommentChecker } from "./dependencies" import { getGhCliInfo } from "./tools-gh" -import { getLspServerStats, getLspServersInfo } from "./tools-lsp" +import { getInstalledLspServers } from "./tools-lsp" import { getBuiltinMcpInfo, getUserMcpInfo } from "./tools-mcp" import { CHECK_IDS, CHECK_NAMES } from "../constants" import type { CheckResult, DoctorIssue, ToolsSummary } from "../types" @@ -13,14 +13,12 @@ export async function gatherToolsSummary(): Promise { getGhCliInfo(), ]) - const lspServers = getLspServersInfo() - const lspStats = getLspServerStats(lspServers) + const lspServers = getInstalledLspServers() const builtinMcp = getBuiltinMcpInfo() const userMcp = getUserMcpInfo() return { - lspInstalled: lspStats.installed, - lspTotal: lspStats.total, + lspServers, astGrepCli: astGrepCliInfo.installed, astGrepNapi: astGrepNapiInfo.installed, commentChecker: commentCheckerInfo.installed, @@ -57,7 +55,7 @@ function buildToolIssues(summary: ToolsSummary): DoctorIssue[] { }) } - if (summary.lspInstalled === 0) { + if (summary.lspServers.length === 0) { issues.push({ title: "No LSP servers detected", description: "LSP-dependent tools will be limited until at least one server is installed.", @@ -109,7 +107,7 @@ export async function checkTools(): Promise { details: [ `AST-Grep: cli=${summary.astGrepCli ? "yes" : "no"}, napi=${summary.astGrepNapi ? "yes" : "no"}`, `Comment checker: ${summary.commentChecker ? "yes" : "no"}`, - `LSP: ${summary.lspInstalled}/${summary.lspTotal}`, + `LSP: ${summary.lspServers.length > 0 ? `${summary.lspServers.length} server(s)` : "none"}`, `GH CLI: ${summary.ghCli.installed ? "installed" : "missing"}${summary.ghCli.authenticated ? " (authenticated)" : ""}`, `MCP: builtin=${summary.mcpBuiltin.length}, user=${summary.mcpUser.length}`, ], diff --git a/src/cli/doctor/format-default.test.ts b/src/cli/doctor/format-default.test.ts index 76b851695..0c7883cfd 100644 --- a/src/cli/doctor/format-default.test.ts +++ b/src/cli/doctor/format-default.test.ts @@ -20,8 +20,7 @@ function createBaseResult(): DoctorResult { isLocalDev: false, }, tools: { - lspInstalled: 0, - lspTotal: 0, + lspServers: [], astGrepCli: false, astGrepNapi: false, commentChecker: false, diff --git a/src/cli/doctor/format-status.ts b/src/cli/doctor/format-status.ts index 6b4930a1b..a993b500e 100644 --- a/src/cli/doctor/format-status.ts +++ b/src/cli/doctor/format-status.ts @@ -19,11 +19,13 @@ export function formatStatus(result: DoctorResult): string { const configStatus = systemInfo.configValid ? color.green("(valid)") : color.red("(invalid)") lines.push(` ${padding}Config ${configPath} ${configStatus}`) - const lspText = `LSP ${tools.lspInstalled}/${tools.lspTotal}` + const serverCount = tools.lspServers.length + const lspMark = formatStatusMark(serverCount > 0) + const lspText = serverCount > 0 ? `${serverCount} server${serverCount === 1 ? "" : "s"}` : "none" const astGrepMark = formatStatusMark(tools.astGrepCli) const ghMark = formatStatusMark(tools.ghCli.installed && tools.ghCli.authenticated) const ghUser = tools.ghCli.username ?? "" - lines.push(` ${padding}Tools ${lspText} · AST-Grep ${astGrepMark} · gh ${ghMark}${ghUser ? ` (${ghUser})` : ""}`) + lines.push(` ${padding}Tools LSP ${lspMark} ${lspText} · AST-Grep ${astGrepMark} · gh ${ghMark}${ghUser ? ` (${ghUser})` : ""}`) const builtinCount = tools.mcpBuiltin.length const userCount = tools.mcpUser.length diff --git a/src/cli/doctor/format-verbose.ts b/src/cli/doctor/format-verbose.ts index f965b6759..b0c767cb0 100644 --- a/src/cli/doctor/format-verbose.ts +++ b/src/cli/doctor/format-verbose.ts @@ -33,7 +33,15 @@ export function formatVerbose(result: DoctorResult): string { lines.push(`${color.bold("Tools")}`) lines.push(`${color.dim("\u2500".repeat(40))}`) - lines.push(` ${formatStatusSymbol("pass")} LSP ${tools.lspInstalled}/${tools.lspTotal} installed`) + if (tools.lspServers.length === 0) { + lines.push(` ${formatStatusSymbol("warn")} LSP none detected`) + } else { + const allExts = [...new Set(tools.lspServers.flatMap((s) => s.extensions))].sort() + lines.push(` ${formatStatusSymbol("pass")} LSP ${tools.lspServers.length} server${tools.lspServers.length === 1 ? "" : "s"} (${allExts.join(", ")})`) + for (const server of tools.lspServers) { + lines.push(`${" ".repeat(20)}${server.id} (${server.extensions.join(", ")})`) + } + } lines.push(` ${formatStatusSymbol(tools.astGrepCli ? "pass" : "fail")} ast-grep CLI ${tools.astGrepCli ? "installed" : "not found"}`) lines.push(` ${formatStatusSymbol(tools.astGrepNapi ? "pass" : "fail")} ast-grep napi ${tools.astGrepNapi ? "installed" : "not found"}`) lines.push(` ${formatStatusSymbol(tools.commentChecker ? "pass" : "fail")} comment-checker ${tools.commentChecker ? "installed" : "not found"}`) diff --git a/src/cli/doctor/formatter.test.ts b/src/cli/doctor/formatter.test.ts index 312ccbedc..522c93d4d 100644 --- a/src/cli/doctor/formatter.test.ts +++ b/src/cli/doctor/formatter.test.ts @@ -19,8 +19,10 @@ function createDoctorResult(): DoctorResult { isLocalDev: false, }, tools: { - lspInstalled: 2, - lspTotal: 4, + lspServers: [ + { id: "typescript", extensions: [".ts", ".tsx", ".js", ".jsx"] }, + { id: "pyright", extensions: [".py", ".pyi"] }, + ], astGrepCli: true, astGrepNapi: false, commentChecker: true, @@ -102,7 +104,7 @@ describe("formatDoctorOutput", () => { const output = stripAnsi(formatDoctorOutput(result, "status")) //#then - expect(output).toContain("LSP 2/4") + expect(output).toContain("LSP") expect(output).toContain("context7") }) }) diff --git a/src/cli/doctor/runner.test.ts b/src/cli/doctor/runner.test.ts index ca96b0794..d3d87e958 100644 --- a/src/cli/doctor/runner.test.ts +++ b/src/cli/doctor/runner.test.ts @@ -16,8 +16,7 @@ function createSystemInfo(): SystemInfo { function createTools(): ToolsSummary { return { - lspInstalled: 1, - lspTotal: 4, + lspServers: [{ id: "typescript", extensions: [".ts", ".tsx", ".js", ".jsx"] }], astGrepCli: true, astGrepNapi: false, commentChecker: true, diff --git a/src/cli/doctor/types.ts b/src/cli/doctor/types.ts index 4c165566c..810720943 100644 --- a/src/cli/doctor/types.ts +++ b/src/cli/doctor/types.ts @@ -47,8 +47,7 @@ export interface SystemInfo { } export interface ToolsSummary { - lspInstalled: number - lspTotal: number + lspServers: Array<{ id: string; extensions: string[] }> astGrepCli: boolean astGrepNapi: boolean commentChecker: boolean