chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,482 @@
import { beforeEach, describe, expect, test } from "bun:test"
import path from "path"
import { pathToFileURL } from "url"
import { tmpdir } from "../fixture/fixture"
import { LSPClient } from "../../src/lsp"
import { LSPServer } from "../../src/lsp"
import { Instance } from "../../src/project/instance"
import { Log } from "../../src/util"
function spawnFakeServer() {
const { spawn } = require("child_process")
const serverPath = path.join(__dirname, "../fixture/lsp/fake-lsp-server.js")
return {
process: spawn(process.execPath, [serverPath], {
stdio: "pipe",
}),
}
}
describe("LSPClient interop", () => {
beforeEach(async () => {
await Log.init({ print: true })
})
test("handles workspace/workspaceFolders request", async () => {
const handle = spawnFakeServer() as any
const client = await Instance.provide({
directory: process.cwd(),
fn: () =>
LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: process.cwd(),
directory: process.cwd(),
}),
})
await client.connection.sendNotification("test/trigger", {
method: "workspace/workspaceFolders",
})
await new Promise((resolve) => setTimeout(resolve, 100))
expect(client.connection).toBeDefined()
await client.shutdown()
})
test("handles client/registerCapability request", async () => {
const handle = spawnFakeServer() as any
const client = await Instance.provide({
directory: process.cwd(),
fn: () =>
LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: process.cwd(),
directory: process.cwd(),
}),
})
await client.connection.sendNotification("test/trigger", {
method: "client/registerCapability",
})
await new Promise((resolve) => setTimeout(resolve, 100))
expect(client.connection).toBeDefined()
await client.shutdown()
})
test("handles client/unregisterCapability request", async () => {
const handle = spawnFakeServer() as any
const client = await Instance.provide({
directory: process.cwd(),
fn: () =>
LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: process.cwd(),
directory: process.cwd(),
}),
})
await client.connection.sendNotification("test/trigger", {
method: "client/unregisterCapability",
})
await new Promise((resolve) => setTimeout(resolve, 100))
expect(client.connection).toBeDefined()
await client.shutdown()
})
test("initialize does not overclaim unsupported diagnostics capabilities", async () => {
const handle = spawnFakeServer() as any
const client = await Instance.provide({
directory: process.cwd(),
fn: () =>
LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: process.cwd(),
directory: process.cwd(),
}),
})
const params = await client.connection.sendRequest<any>("test/get-initialize-params", {})
expect(params.capabilities.workspace.diagnostics.refreshSupport).toBe(false)
expect(params.capabilities.textDocument.publishDiagnostics.versionSupport).toBe(false)
await client.shutdown()
})
test("workspace/configuration returns one result per requested item", async () => {
const handle = spawnFakeServer() as any
const initialization = {
alpha: {
beta: 1,
},
gamma: true,
}
const client = await Instance.provide({
directory: process.cwd(),
fn: () =>
LSPClient.create({
serverID: "fake",
server: {
...(handle as unknown as LSPServer.Handle),
initialization,
},
root: process.cwd(),
directory: process.cwd(),
}),
})
const response = await client.connection.sendRequest<any[]>("test/request-configuration", {
items: [{ section: "alpha" }, { section: "alpha.beta" }, { section: "missing" }, {}],
})
expect(response).toEqual([{ beta: 1 }, 1, null, initialization])
await client.shutdown()
})
test("sends ranged didChange for incremental sync servers", async () => {
const handle = spawnFakeServer() as any
await using tmp = await tmpdir()
const file = path.join(tmp.path, "client.ts")
await Bun.write(file, "first\n")
await Instance.provide({
directory: tmp.path,
fn: async () => {
const client = await LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: tmp.path,
directory: tmp.path,
})
await client.notify.open({ path: file })
await Bun.write(file, "second\nthird\n")
await client.notify.open({ path: file })
const change = await client.connection.sendRequest<{
textDocument: { version: number }
contentChanges: {
range?: { start: { line: number; character: number }; end: { line: number; character: number } }
text: string
}[]
}>("test/get-last-change", {})
expect(change.textDocument.version).toBe(1)
expect(change.contentChanges).toEqual([
{
range: {
start: { line: 0, character: 0 },
end: { line: 1, character: 0 },
},
text: "second\nthird\n",
},
])
await client.shutdown()
},
})
})
test("document mode falls back to push diagnostics", async () => {
const handle = spawnFakeServer() as any
await using tmp = await tmpdir()
const file = path.join(tmp.path, "client.ts")
await Bun.write(file, "const x = 1\n")
await Instance.provide({
directory: tmp.path,
fn: async () => {
const client = await LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: tmp.path,
directory: tmp.path,
})
const version = await client.notify.open({ path: file })
const wait = client.waitForDiagnostics({ path: file, version, mode: "document" })
await client.connection.sendNotification("test/publish-diagnostics", {
uri: pathToFileURL(file).href,
version,
diagnostics: [
{
range: {
start: { line: 0, character: 0 },
end: { line: 0, character: 5 },
},
message: "push diagnostic",
severity: 1,
},
],
})
await wait
const diagnostics = client.diagnostics.get(file) ?? []
expect(diagnostics).toHaveLength(1)
expect(diagnostics[0]?.message).toBe("push diagnostic")
const count = await client.connection.sendRequest("test/get-diagnostic-request-count", {})
expect(count).toBe(0)
await client.shutdown()
},
})
})
test("document mode accepts matching push diagnostics published before waiting", async () => {
const handle = spawnFakeServer() as any
await using tmp = await tmpdir()
const file = path.join(tmp.path, "client.ts")
await Bun.write(file, "const x = 1\n")
await Instance.provide({
directory: tmp.path,
fn: async () => {
const client = await LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: tmp.path,
directory: tmp.path,
})
const version = await client.notify.open({ path: file })
await client.connection.sendNotification("test/publish-diagnostics", {
uri: pathToFileURL(file).href,
version,
diagnostics: [
{
range: {
start: { line: 0, character: 0 },
end: { line: 0, character: 5 },
},
message: "push diagnostic",
severity: 1,
},
],
})
for (let i = 0; i < 20 && (client.diagnostics.get(file)?.length ?? 0) === 0; i++) {
await new Promise((resolve) => setTimeout(resolve, 25))
}
expect(client.diagnostics.get(file)?.[0]?.message).toBe("push diagnostic")
const started = Date.now()
await client.waitForDiagnostics({ path: file, version, mode: "document" })
expect(Date.now() - started).toBeLessThan(1_000)
await client.shutdown()
},
})
})
test("document mode waits for pull diagnostics", async () => {
const handle = spawnFakeServer() as any
await using tmp = await tmpdir()
const file = path.join(tmp.path, "client.cs")
await Bun.write(file, "class C {}\n")
await Instance.provide({
directory: tmp.path,
fn: async () => {
const client = await LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: tmp.path,
directory: tmp.path,
})
await client.connection.sendRequest("test/configure-pull-diagnostics", {
registerOn: "didOpen",
registrations: [{ identifier: "DocumentCompilerSemantic" }],
documentDiagnosticsByIdentifier: {
DocumentCompilerSemantic: [
{
range: {
start: { line: 0, character: 0 },
end: { line: 0, character: 5 },
},
message: "pull diagnostic",
severity: 1,
},
],
},
})
const version = await client.notify.open({ path: file })
await client.waitForDiagnostics({ path: file, version, mode: "document" })
const diagnostics = client.diagnostics.get(file) ?? []
expect(diagnostics).toHaveLength(1)
expect(diagnostics[0]?.message).toBe("pull diagnostic")
const count = await client.connection.sendRequest("test/get-diagnostic-request-count", {})
expect(count).toBeGreaterThan(0)
await client.shutdown()
},
})
})
test("document mode does not wait for the slowest pull identifier after current-file diagnostics arrive", async () => {
const handle = spawnFakeServer() as any
await using tmp = await tmpdir()
const file = path.join(tmp.path, "client.cs")
await Bun.write(file, "class C {}\n")
await Instance.provide({
directory: tmp.path,
fn: async () => {
const client = await LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: tmp.path,
directory: tmp.path,
})
await client.connection.sendRequest("test/configure-pull-diagnostics", {
registrations: [{ identifier: "fast" }, { identifier: "slow" }],
documentDiagnosticsByIdentifier: {
fast: [
{
range: {
start: { line: 0, character: 0 },
end: { line: 0, character: 5 },
},
message: "fast diagnostic",
severity: 1,
},
],
slow: [],
},
documentDelayMsByIdentifier: {
slow: 2_500,
},
})
const version = await client.notify.open({ path: file })
await client.connection.sendRequest("test/register-configured-pull-diagnostics", {})
await new Promise((resolve) => setTimeout(resolve, 100))
const started = Date.now()
await client.waitForDiagnostics({ path: file, version, mode: "document" })
expect(Date.now() - started).toBeLessThan(1_000)
expect(client.diagnostics.get(file)?.[0]?.message).toBe("fast diagnostic")
expect(await client.connection.sendRequest("test/get-diagnostic-request-count", {})).toBeGreaterThan(1)
await client.shutdown()
},
})
})
test("full mode includes workspace pull diagnostics", async () => {
const handle = spawnFakeServer() as any
await using tmp = await tmpdir()
const file = path.join(tmp.path, "client.cs")
const related = path.join(tmp.path, "other.cs")
await Bun.write(file, "class C {}\n")
await Bun.write(related, "class D {}\n")
await Instance.provide({
directory: tmp.path,
fn: async () => {
const client = await LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: tmp.path,
directory: tmp.path,
})
await client.connection.sendRequest("test/configure-pull-diagnostics", {
registerOn: "didOpen",
registrations: [
{ identifier: "DocumentCompilerSemantic" },
{ identifier: "WorkspaceDocumentsAndProject", workspaceDiagnostics: true },
],
documentDiagnosticsByIdentifier: {
DocumentCompilerSemantic: [
{
range: {
start: { line: 0, character: 0 },
end: { line: 0, character: 5 },
},
message: "current file",
severity: 1,
},
],
},
workspaceDiagnosticsByIdentifier: {
WorkspaceDocumentsAndProject: [
{
uri: pathToFileURL(related).href,
items: [
{
range: {
start: { line: 0, character: 0 },
end: { line: 0, character: 5 },
},
message: "workspace file",
severity: 1,
},
],
},
],
},
})
const version = await client.notify.open({ path: file })
await client.waitForDiagnostics({ path: file, version, mode: "full" })
expect(client.diagnostics.get(file)?.[0]?.message).toBe("current file")
expect(client.diagnostics.get(related)?.[0]?.message).toBe("workspace file")
await client.shutdown()
},
})
})
test("full mode treats an empty workspace pull response as handled", async () => {
const handle = spawnFakeServer() as any
await using tmp = await tmpdir()
const file = path.join(tmp.path, "client.cs")
await Bun.write(file, "class C {}\n")
await Instance.provide({
directory: tmp.path,
fn: async () => {
const client = await LSPClient.create({
serverID: "fake",
server: handle as unknown as LSPServer.Handle,
root: tmp.path,
directory: tmp.path,
})
await client.connection.sendRequest("test/configure-pull-diagnostics", {
registerOn: "didOpen",
registrations: [{ identifier: "WorkspaceDocumentsAndProject", workspaceDiagnostics: true }],
workspaceDiagnosticsByIdentifier: {
WorkspaceDocumentsAndProject: [],
},
})
const version = await client.notify.open({ path: file })
const started = Date.now()
await client.waitForDiagnostics({ path: file, version, mode: "full" })
expect(Date.now() - started).toBeLessThan(1_000)
await client.shutdown()
},
})
})
})

View File

@@ -0,0 +1,109 @@
import { describe, expect, spyOn } from "bun:test"
import path from "path"
import { Effect, Layer } from "effect"
import { LSP } from "../../src/lsp"
import { LSPServer } from "../../src/lsp"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
const it = testEffect(Layer.mergeAll(LSP.defaultLayer, CrossSpawnSpawner.defaultLayer))
describe("lsp.spawn", () => {
it.live("does not spawn builtin LSP for files outside instance", () =>
provideTmpdirInstance(
(dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const spy = spyOn(LSPServer.Typescript, "spawn").mockResolvedValue(undefined)
try {
yield* lsp.touchFile(path.join(dir, "..", "outside.ts"))
yield* lsp.hover({
file: path.join(dir, "..", "hover.ts"),
line: 0,
character: 0,
})
expect(spy).toHaveBeenCalledTimes(0)
} finally {
spy.mockRestore()
}
}),
),
{ config: { lsp: true } },
),
)
it.live("does not spawn builtin LSP for files inside instance when LSP is unset", () =>
provideTmpdirInstance((dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const spy = spyOn(LSPServer.Typescript, "spawn").mockResolvedValue(undefined)
try {
yield* lsp.hover({
file: path.join(dir, "src", "inside.ts"),
line: 0,
character: 0,
})
expect(spy).toHaveBeenCalledTimes(0)
} finally {
spy.mockRestore()
}
}),
),
),
)
it.live("would spawn builtin LSP for files inside instance when lsp is true", () =>
provideTmpdirInstance(
(dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const spy = spyOn(LSPServer.Typescript, "spawn").mockResolvedValue(undefined)
try {
yield* lsp.hover({
file: path.join(dir, "src", "inside.ts"),
line: 0,
character: 0,
})
expect(spy).toHaveBeenCalledTimes(1)
} finally {
spy.mockRestore()
}
}),
),
{ config: { lsp: true } },
),
)
it.live("would spawn builtin LSP for files inside instance when config object is provided", () =>
provideTmpdirInstance(
(dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const spy = spyOn(LSPServer.Typescript, "spawn").mockResolvedValue(undefined)
try {
yield* lsp.hover({
file: path.join(dir, "src", "inside.ts"),
line: 0,
character: 0,
})
expect(spy).toHaveBeenCalledTimes(1)
} finally {
spy.mockRestore()
}
}),
),
{
config: {
lsp: {
eslint: { disabled: true },
},
},
},
),
)
})

View File

@@ -0,0 +1,22 @@
import { describe, expect, test } from "bun:test"
import fs from "fs/promises"
import path from "path"
import { spawn } from "../../src/lsp/launch"
import { tmpdir } from "../fixture/fixture"
describe("lsp.launch", () => {
test("spawns cmd scripts with spaces on Windows", async () => {
if (process.platform !== "win32") return
await using tmp = await tmpdir()
const dir = path.join(tmp.path, "with space")
const file = path.join(dir, "echo cmd.cmd")
await fs.mkdir(dir, { recursive: true })
await Bun.write(file, "@echo off\r\nif %~1==--stdio exit /b 0\r\nexit /b 7\r\n")
const proc = spawn(file, ["--stdio"])
expect(await proc.exited).toBe(0)
})
})

View File

@@ -0,0 +1,184 @@
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"
import path from "path"
import { Effect, Layer } from "effect"
import { LSP } from "../../src/lsp"
import { LSPServer } from "../../src/lsp"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
const it = testEffect(Layer.mergeAll(LSP.defaultLayer, CrossSpawnSpawner.defaultLayer))
describe("LSP service lifecycle", () => {
let spawnSpy: ReturnType<typeof spyOn>
beforeEach(() => {
spawnSpy = spyOn(LSPServer.Typescript, "spawn").mockResolvedValue(undefined)
})
afterEach(() => {
spawnSpy.mockRestore()
})
it.live("init() completes without error", () => provideTmpdirInstance(() => LSP.Service.use((lsp) => lsp.init())))
it.live("status() returns empty array initially", () =>
provideTmpdirInstance(() =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const result = yield* lsp.status()
expect(Array.isArray(result)).toBe(true)
expect(result.length).toBe(0)
}),
),
),
)
it.live("diagnostics() returns empty object initially", () =>
provideTmpdirInstance(() =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const result = yield* lsp.diagnostics()
expect(typeof result).toBe("object")
expect(Object.keys(result).length).toBe(0)
}),
),
),
)
it.live("hasClients() returns false for .ts files in instance when LSP is unset", () =>
provideTmpdirInstance((dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const result = yield* lsp.hasClients(path.join(dir, "test.ts"))
expect(result).toBe(false)
}),
),
),
)
it.live("hasClients() returns true for .ts files in instance when lsp is true", () =>
provideTmpdirInstance(
(dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const result = yield* lsp.hasClients(path.join(dir, "test.ts"))
expect(result).toBe(true)
}),
),
{ config: { lsp: true } },
),
)
it.live("hasClients() keeps built-in LSPs when config object is provided", () =>
provideTmpdirInstance(
(dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const result = yield* lsp.hasClients(path.join(dir, "test.ts"))
expect(result).toBe(true)
}),
),
{
config: {
lsp: {
eslint: { disabled: true },
},
},
},
),
)
it.live("hasClients() returns false for files outside instance", () =>
provideTmpdirInstance((dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const result = yield* lsp.hasClients(path.join(dir, "..", "outside.ts"))
expect(typeof result).toBe("boolean")
}),
),
),
)
it.live("workspaceSymbol() returns empty array with no clients", () =>
provideTmpdirInstance(() =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const result = yield* lsp.workspaceSymbol("test")
expect(Array.isArray(result)).toBe(true)
expect(result.length).toBe(0)
}),
),
),
)
it.live("definition() returns empty array for unknown file", () =>
provideTmpdirInstance((dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const result = yield* lsp.definition({
file: path.join(dir, "nonexistent.ts"),
line: 0,
character: 0,
})
expect(Array.isArray(result)).toBe(true)
}),
),
),
)
it.live("references() returns empty array for unknown file", () =>
provideTmpdirInstance((dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const result = yield* lsp.references({
file: path.join(dir, "nonexistent.ts"),
line: 0,
character: 0,
})
expect(Array.isArray(result)).toBe(true)
}),
),
),
)
it.live("multiple init() calls are idempotent", () =>
provideTmpdirInstance(() =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
yield* lsp.init()
yield* lsp.init()
yield* lsp.init()
}),
),
),
)
})
describe("LSP.Diagnostic", () => {
test("pretty() formats error diagnostic", () => {
const result = LSP.Diagnostic.pretty({
range: { start: { line: 9, character: 4 }, end: { line: 9, character: 10 } },
message: "Type 'string' is not assignable to type 'number'",
severity: 1,
} as any)
expect(result).toBe("ERROR [10:5] Type 'string' is not assignable to type 'number'")
})
test("pretty() formats warning diagnostic", () => {
const result = LSP.Diagnostic.pretty({
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 5 } },
message: "Unused variable",
severity: 2,
} as any)
expect(result).toBe("WARN [1:1] Unused variable")
})
test("pretty() defaults to ERROR when no severity", () => {
const result = LSP.Diagnostic.pretty({
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
message: "Something wrong",
} as any)
expect(result).toBe("ERROR [1:1] Something wrong")
})
})