refactor: replace string-matching error checks with custom error classes
This commit is contained in:
41
index.ts
41
index.ts
@@ -7,6 +7,10 @@ import { Type } from "typebox";
|
|||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import { uriToPath } from "./src/client.ts";
|
import { uriToPath } from "./src/client.ts";
|
||||||
import { daemonDiagnostics, daemonRequest } from "./src/daemonClient.ts";
|
import { daemonDiagnostics, daemonRequest } from "./src/daemonClient.ts";
|
||||||
|
import {
|
||||||
|
ServerNotFoundError,
|
||||||
|
UnsupportedExtensionError,
|
||||||
|
} from "./src/types.ts";
|
||||||
|
|
||||||
// Format Hover - Turn an LSP hover response into readable text.
|
// Format Hover - Turn an LSP hover response into readable text.
|
||||||
function formatHover(result: unknown): string {
|
function formatHover(result: unknown): string {
|
||||||
@@ -202,6 +206,16 @@ function formatDiagnostics(result: unknown): string {
|
|||||||
.join("\n");
|
.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is Expected Error - Returns true if the error is an expected condition
|
||||||
|
// (unsupported file type or missing server binary) that should be
|
||||||
|
// suppressed rather than surfaced to the user.
|
||||||
|
function isExpectedError(error: unknown): boolean {
|
||||||
|
return (
|
||||||
|
error instanceof UnsupportedExtensionError ||
|
||||||
|
error instanceof ServerNotFoundError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Run LSP Request - Forwards to the daemon, which owns the long-lived
|
// Run LSP Request - Forwards to the daemon, which owns the long-lived
|
||||||
// LspClient cache and handles didOpen/didChange syncing. The daemon
|
// LspClient cache and handles didOpen/didChange syncing. The daemon
|
||||||
// injects textDocument.uri from the file path, so we omit it here.
|
// injects textDocument.uri from the file path, so we omit it here.
|
||||||
@@ -210,13 +224,27 @@ async function runLsp(
|
|||||||
method: string,
|
method: string,
|
||||||
params: Record<string, unknown>,
|
params: Record<string, unknown>,
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
return daemonRequest(filePath, method, params);
|
try {
|
||||||
|
return await daemonRequest(filePath, method, params);
|
||||||
|
} catch (error) {
|
||||||
|
if (isExpectedError(error)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run LSP Diagnostics - Diagnostics arrive as a notification, so the
|
// Run LSP Diagnostics - Diagnostics arrive as a notification, so the
|
||||||
// daemon has a dedicated op that waits for the next publish.
|
// daemon has a dedicated op that waits for the next publish.
|
||||||
async function runDiagnostics(filePath: string): Promise<unknown> {
|
async function runDiagnostics(filePath: string): Promise<unknown> {
|
||||||
return daemonDiagnostics(filePath, 1500);
|
try {
|
||||||
|
return await daemonDiagnostics(filePath, 1500);
|
||||||
|
} catch (error) {
|
||||||
|
if (isExpectedError(error)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shared Parameters Schema - All position-based tools accept file + optional
|
// Shared Parameters Schema - All position-based tools accept file + optional
|
||||||
@@ -412,13 +440,14 @@ export default function (pi: ExtensionAPI) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Silently fail - don't interrupt the flow
|
// Silently fail - don't interrupt the flow
|
||||||
// Only log if there's an actual error we care about
|
// Only log if there's an actual error we care about
|
||||||
if (error && typeof error === "object" && "message" in error) {
|
if (!isExpectedError(error)) {
|
||||||
const msg = (error as { message: string }).message;
|
const msg =
|
||||||
if (!msg.includes("not found on PATH")) {
|
error && typeof error === "object" && "message" in error
|
||||||
|
? (error as { message: string }).message
|
||||||
|
: String(error);
|
||||||
console.error("LSP auto-check failed:", msg);
|
console.error("LSP auto-check failed:", msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Manual Check Command - Run diagnostics on specific files
|
// Manual Check Command - Run diagnostics on specific files
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type {
|
|||||||
} from "vscode-languageserver-protocol";
|
} from "vscode-languageserver-protocol";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import type { ServerConfig } from "./types.ts";
|
import type { ServerConfig } from "./types.ts";
|
||||||
|
import { ServerNotFoundError } from "./types.ts";
|
||||||
import { findRoot, pathToUri, uriToPath } from "./root.ts";
|
import { findRoot, pathToUri, uriToPath } from "./root.ts";
|
||||||
|
|
||||||
// Is On PATH - Returns true if `cmd` resolves to an executable via the
|
// Is On PATH - Returns true if `cmd` resolves to an executable via the
|
||||||
@@ -73,10 +74,7 @@ export class LspClient {
|
|||||||
// letting spawn ENOENT surface as a generic error. It's the user's
|
// letting spawn ENOENT surface as a generic error. It's the user's
|
||||||
// responsibility to have the server installed & on PATH.
|
// responsibility to have the server installed & on PATH.
|
||||||
if (!isOnPath(this.server.command)) {
|
if (!isOnPath(this.server.command)) {
|
||||||
throw new Error(
|
throw new ServerNotFoundError(this.server.command);
|
||||||
`LSP server binary "${this.server.command}" not found on PATH. ` +
|
|
||||||
`Install it and ensure it's on your PATH (required by server "${this.server.id}").`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this.proc = spawn(this.server.command, this.server.args, {
|
this.proc = spawn(this.server.command, this.server.args, {
|
||||||
stdio: ["pipe", "pipe", "pipe"],
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as path from "node:path";
|
|||||||
import { pathToFileURL, fileURLToPath } from "node:url";
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
||||||
import { servers } from "../server.ts";
|
import { servers } from "../server.ts";
|
||||||
import type { ServerConfig } from "./types.ts";
|
import type { ServerConfig } from "./types.ts";
|
||||||
|
import { UnsupportedExtensionError } from "./types.ts";
|
||||||
|
|
||||||
// Resolve File URI To Local Path
|
// Resolve File URI To Local Path
|
||||||
export function uriToPath(uri: string): string {
|
export function uriToPath(uri: string): string {
|
||||||
@@ -20,7 +21,7 @@ export function pickServer(filePath: string): ServerConfig {
|
|||||||
const ext = path.extname(filePath).replace(/^\./, "");
|
const ext = path.extname(filePath).replace(/^\./, "");
|
||||||
const hit = servers.find((s) => s.match.includes(ext));
|
const hit = servers.find((s) => s.match.includes(ext));
|
||||||
if (!hit) {
|
if (!hit) {
|
||||||
throw new Error(`No LSP server registered for extension ".${ext}"`);
|
throw new UnsupportedExtensionError(`.${ext}`);
|
||||||
}
|
}
|
||||||
return hit;
|
return hit;
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/types.ts
19
src/types.ts
@@ -1,3 +1,22 @@
|
|||||||
|
// LSP Errors - Custom error classes so callers can distinguish expected
|
||||||
|
// conditions (unsupported file type, missing binary) from unexpected ones.
|
||||||
|
export class UnsupportedExtensionError extends Error {
|
||||||
|
constructor(ext: string) {
|
||||||
|
super(`No LSP server registered for extension "${ext}"`);
|
||||||
|
this.name = "UnsupportedExtensionError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServerNotFoundError extends Error {
|
||||||
|
constructor(command: string) {
|
||||||
|
super(
|
||||||
|
`LSP server binary "${command}" not found on PATH. ` +
|
||||||
|
`Install it and ensure it's on your PATH.`,
|
||||||
|
);
|
||||||
|
this.name = "ServerNotFoundError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServerConfig {
|
export interface ServerConfig {
|
||||||
// Stable identifier (useful for logs and future daemon cache keys).
|
// Stable identifier (useful for logs and future daemon cache keys).
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user