| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- import { describe, expect, spyOn } from "bun:test"
- import path from "path"
- import { LayerNode } from "@kirincode-ai/core/effect/layer-node"
- import { Deferred, Effect, Layer } from "effect"
- import { EventV2Bridge } from "@/event-v2-bridge"
- import { Config } from "@/config/config"
- import { RuntimeFlags } from "@/effect/runtime-flags"
- import { LSP } from "@/lsp/lsp"
- import * as LSPServer from "@/lsp/server"
- import { CrossSpawnSpawner } from "@kirincode-ai/core/cross-spawn-spawner"
- import { TestInstance } from "../fixture/fixture"
- import { awaitWithTimeout, testEffect } from "../lib/effect"
- const lspLayer = (flags: Parameters<typeof RuntimeFlags.layer>[0] = {}) =>
- LayerNode.compile(LayerNode.group([LSP.node, Config.node, RuntimeFlags.node, EventV2Bridge.node]), [
- [RuntimeFlags.node, RuntimeFlags.layer(flags)],
- ])
- const it = testEffect(Layer.mergeAll(lspLayer(), LayerNode.compile(CrossSpawnSpawner.node)))
- const experimentalTyIt = testEffect(
- Layer.mergeAll(lspLayer({ experimentalLspTy: true }), LayerNode.compile(CrossSpawnSpawner.node)),
- )
- const fakeServerPath = path.join(__dirname, "../fixture/lsp/fake-lsp-server.js")
- const disabledDownloadIt = testEffect(
- Layer.mergeAll(lspLayer({ disableLspDownload: true }), LayerNode.compile(CrossSpawnSpawner.node)),
- )
- describe("lsp.spawn", () => {
- it.instance(
- "does not spawn builtin LSP for files outside instance",
- () =>
- LSP.Service.use((lsp) =>
- Effect.gen(function* () {
- const dir = (yield* TestInstance).directory
- 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.instance("does not spawn builtin LSP for files inside instance when LSP is unset", () =>
- LSP.Service.use((lsp) =>
- Effect.gen(function* () {
- const dir = (yield* TestInstance).directory
- 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.instance(
- "would spawn builtin LSP for files inside instance when lsp is true",
- () =>
- LSP.Service.use((lsp) =>
- Effect.gen(function* () {
- const dir = (yield* TestInstance).directory
- 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.instance(
- "publishes lsp.updated after custom LSP initialization",
- () =>
- Effect.gen(function* () {
- const dir = (yield* TestInstance).directory
- const lsp = yield* LSP.Service
- const updated = yield* Deferred.make<void>()
- const events = yield* EventV2Bridge.Service
- const unsubscribe = yield* events.listen((event) => {
- if (event.type === LSP.Event.Updated.type) Deferred.doneUnsafe(updated, Effect.void)
- return Effect.void
- })
- yield* Effect.addFinalizer(() => unsubscribe)
- const file = path.join(dir, "sample.repro")
- yield* Effect.promise(() => Bun.write(file, "sample\n"))
- yield* lsp.touchFile(file)
- yield* awaitWithTimeout(Deferred.await(updated), "lsp.updated event was not published")
- }),
- {
- config: {
- lsp: {
- fake: {
- command: [process.execPath, fakeServerPath],
- extensions: [".repro"],
- },
- },
- },
- },
- )
- it.instance(
- "would spawn builtin LSP for files inside instance when config object is provided",
- () =>
- LSP.Service.use((lsp) =>
- Effect.gen(function* () {
- const dir = (yield* TestInstance).directory
- 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 },
- },
- },
- },
- )
- it.instance(
- "uses pyright instead of ty by default",
- () =>
- LSP.Service.use((lsp) =>
- Effect.gen(function* () {
- const dir = (yield* TestInstance).directory
- const ty = spyOn(LSPServer.Ty, "spawn").mockResolvedValue(undefined)
- const pyright = spyOn(LSPServer.Pyright, "spawn").mockResolvedValue(undefined)
- try {
- yield* lsp.hover({
- file: path.join(dir, "src", "inside.py"),
- line: 0,
- character: 0,
- })
- expect(ty).toHaveBeenCalledTimes(0)
- expect(pyright).toHaveBeenCalledTimes(1)
- } finally {
- ty.mockRestore()
- pyright.mockRestore()
- }
- }),
- ),
- { config: { lsp: true } },
- )
- experimentalTyIt.instance(
- "uses ty instead of pyright when experimentalLspTy is enabled",
- () =>
- LSP.Service.use((lsp) =>
- Effect.gen(function* () {
- const dir = (yield* TestInstance).directory
- const ty = spyOn(LSPServer.Ty, "spawn").mockResolvedValue(undefined)
- const pyright = spyOn(LSPServer.Pyright, "spawn").mockResolvedValue(undefined)
- try {
- yield* lsp.hover({
- file: path.join(dir, "src", "inside.py"),
- line: 0,
- character: 0,
- })
- expect(ty).toHaveBeenCalledTimes(1)
- expect(pyright).toHaveBeenCalledTimes(0)
- } finally {
- ty.mockRestore()
- pyright.mockRestore()
- }
- }),
- ),
- { config: { lsp: true } },
- )
- disabledDownloadIt.instance(
- "passes disableLspDownload to builtin LSP spawn",
- () =>
- LSP.Service.use((lsp) =>
- Effect.gen(function* () {
- const dir = (yield* TestInstance).directory
- const pyright = spyOn(LSPServer.Pyright, "spawn").mockResolvedValue(undefined)
- try {
- yield* lsp.hover({
- file: path.join(dir, "src", "inside.py"),
- line: 0,
- character: 0,
- })
- expect(pyright).toHaveBeenCalledTimes(1)
- expect(pyright.mock.calls[0]?.[2]).toMatchObject({ disableLspDownload: true })
- } finally {
- pyright.mockRestore()
- }
- }),
- ),
- { config: { lsp: true } },
- )
- })
|