Merge pull request #2588 from acamq/refactor/doctor-lsp-extensions

refactor(doctor): show detected LSP extensions instead of hardcoded server counts
This commit is contained in:
acamq
2026-03-15 19:12:48 -06:00
committed by GitHub
8 changed files with 32 additions and 48 deletions

View File

@@ -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 }))
}

View File

@@ -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<ToolsSummary> {
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<CheckResult> {
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}`,
],

View File

@@ -20,8 +20,7 @@ function createBaseResult(): DoctorResult {
isLocalDev: false,
},
tools: {
lspInstalled: 0,
lspTotal: 0,
lspServers: [],
astGrepCli: false,
astGrepNapi: false,
commentChecker: false,

View File

@@ -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

View File

@@ -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 count = tools.lspServers.length
lines.push(` ${formatStatusSymbol("pass")} LSP ${count} server${count === 1 ? "" : "s"}`)
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"}`)

View File

@@ -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,
@@ -119,7 +121,7 @@ describe("formatDoctorOutput", () => {
const output = stripAnsi(formatDoctorOutput(result, "status"))
//#then
expect(output).toContain("LSP 2/4")
expect(output).toContain("LSP")
expect(output).toContain("context7")
})
})

View File

@@ -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,

View File

@@ -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
@@ -126,13 +125,6 @@ export interface DependencyInfo {
installHint?: string
}
export interface LspServerInfo {
id: string
installed: boolean
extensions: string[]
source: "builtin" | "config" | "plugin"
}
export interface McpServerInfo {
id: string
type: "builtin" | "user"