import { describe, expect, it } from "vitest"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { nearestRoot, strictNearestRoot, type RootContext } from "./root.js"; function makeProject(): { dir: string; cleanup: () => void } { const dir = mkdtempSync(path.join(tmpdir(), "pi-lsp-root-")); return { dir, cleanup: () => rmSync(dir, { recursive: true, force: true }), }; } function ctx(directory: string): RootContext { return { directory }; } describe("nearestRoot", () => { it("finds a marker in the file's own directory", async () => { const p = makeProject(); try { writeFileSync(path.join(p.dir, "package.json"), "{}"); const root = await nearestRoot(["package.json"])(path.join(p.dir, "a.ts"), ctx(p.dir)); expect(root).toBe(p.dir); } finally { p.cleanup(); } }); it("finds a marker in a parent directory", async () => { const p = makeProject(); try { writeFileSync(path.join(p.dir, "package.json"), "{}"); const nested = path.join(p.dir, "src", "deep"); mkdirSync(nested, { recursive: true }); const root = await nearestRoot(["package.json"])(path.join(nested, "a.ts"), ctx(p.dir)); expect(root).toBe(p.dir); } finally { p.cleanup(); } }); it("falls back to the project directory when no marker exists", async () => { const p = makeProject(); try { const root = await nearestRoot(["package.json"])(path.join(p.dir, "a.ts"), ctx(p.dir)); expect(root).toBe(p.dir); } finally { p.cleanup(); } }); it("never finds a marker above the project boundary", async () => { const parent = mkdtempSync(path.join(tmpdir(), "pi-lsp-root-parent-")); try { writeFileSync(path.join(parent, "package.json"), "{}"); const nested = path.join(parent, "sub", "deep"); mkdirSync(nested, { recursive: true }); // Project boundary is `parent/sub`; the marker sits above it. const root = await nearestRoot(["package.json"])(path.join(nested, "a.ts"), ctx(path.join(parent, "sub"))); expect(root).toBe(path.join(parent, "sub")); } finally { rmSync(parent, { recursive: true, force: true }); } }); it("returns undefined when an exclude marker is present", async () => { const p = makeProject(); try { writeFileSync(path.join(p.dir, "package.json"), "{}"); writeFileSync(path.join(p.dir, "deno.json"), "{}"); const root = await nearestRoot(["package.json"], { exclude: ["deno.json"] })( path.join(p.dir, "a.ts"), ctx(p.dir), ); expect(root).toBeUndefined(); } finally { p.cleanup(); } }); }); describe("strictNearestRoot", () => { it("returns undefined when no marker exists", async () => { const p = makeProject(); try { const root = await strictNearestRoot(["package.json"])(path.join(p.dir, "a.ts"), ctx(p.dir)); expect(root).toBeUndefined(); } finally { p.cleanup(); } }); it("returns the marker directory when one exists", async () => { const p = makeProject(); try { writeFileSync(path.join(p.dir, "go.mod"), "module x"); const root = await strictNearestRoot(["go.mod"])(path.join(p.dir, "main.go"), ctx(p.dir)); expect(root).toBe(p.dir); } finally { p.cleanup(); } }); });