fix(lsp): add legacy alias for renamed kotlin-language-server key

Users with existing lsp.json overrides referencing the old
"kotlin-language-server" key would silently lose their Kotlin
LSP config after the rename to "kotlin-lsp". LEGACY_ALIASES
map remaps old keys during mergeServers() so overrides still
merge correctly.
This commit is contained in:
Jeremy 2026-04-04 11:45:58 -05:00
parent 851bb0bebe
commit 4df55a51c8
2 changed files with 77 additions and 1 deletions

View file

@ -12,6 +12,11 @@ import type { ServerConfig } from "./types.js";
const require = createRequire(import.meta.url);
const DEFAULTS = require("./defaults.json") as Record<string, Partial<ServerConfig>>;
/** Map legacy server keys to their current names so user overrides still merge. */
const LEGACY_ALIASES: Record<string, string> = {
"kotlin-language-server": "kotlin-lsp",
};
export interface LspConfig {
servers: Record<string, ServerConfig>;
/** Idle timeout in milliseconds. If set, LSP clients will be shutdown after this period of inactivity. Disabled by default. */
@ -109,7 +114,8 @@ function mergeServers(
overrides: Record<string, Partial<ServerConfig>>,
): Record<string, ServerConfig> {
const merged: Record<string, ServerConfig> = { ...base };
for (const [name, config] of Object.entries(overrides)) {
for (const [rawName, config] of Object.entries(overrides)) {
const name = LEGACY_ALIASES[rawName] ?? rawName;
if (merged[name]) {
const candidate = { ...merged[name], ...config };
const normalized = normalizeServerConfig(name, candidate);

View file

@ -0,0 +1,70 @@
// GSD2 — Regression test for LSP legacy server key aliases
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
/**
* When a default server key is renamed (e.g., kotlin-language-server kotlin-lsp),
* user overrides referencing the old key must still merge correctly via LEGACY_ALIASES.
*
* This test exercises the merge path through loadConfig() with a temp project
* containing an lsp.json that uses the legacy key.
*/
import { describe, it, beforeEach, afterEach } from "node:test";
import assert from "node:assert/strict";
import * as fs from "node:fs";
import * as path from "node:path";
import * as os from "node:os";
import { loadConfig } from "./config.js";
describe("LSP legacy server key aliases", () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "lsp-alias-test-"));
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
it("merges user override with legacy key 'kotlin-language-server' into 'kotlin-lsp'", () => {
// Write an lsp.json that uses the old key name with a command that exists (node)
// so resolveCommand doesn't filter it out.
const overrideConfig = {
servers: {
"kotlin-language-server": {
command: "node",
},
},
};
fs.writeFileSync(
path.join(tmpDir, "lsp.json"),
JSON.stringify(overrideConfig),
);
// Also add root markers so the server is detected
fs.writeFileSync(path.join(tmpDir, "build.gradle.kts"), "");
const config = loadConfig(tmpDir);
// The merged config should have kotlin-lsp (new key) with the user's command override
const kotlinServer = config.servers["kotlin-lsp"];
assert.ok(kotlinServer, "kotlin-lsp should exist in merged config");
assert.equal(
kotlinServer.command,
"node",
"command should be overridden from user config via legacy alias",
);
assert.ok(
kotlinServer.fileTypes.includes(".kt"),
"fileTypes should be inherited from defaults",
);
// The old key should NOT appear as a separate entry
assert.equal(
config.servers["kotlin-language-server"],
undefined,
"legacy key should not appear as separate server",
);
});
});