testScratchQuickFixes

tcmts.cli.test.e2e.lsp.testScratchQuickFixes · #xclu65mw · term

Quick fixes in a scratch file: a codebase name it doesn't import yet is imported from where it is, and a name it imports is edited here too, added to the file.

import { plainEnv, tcmBin, tempDir, withChildren } from "#codebase/tcmts/cli/test/e2e/harness";
import { client } from "#codebase/tcmts/cli/test/e2e/lsp";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";

export const testScratchQuickFixes = () => withChildren(async (keep) => {
  const dir = tempDir("lsp fixes");
  const tcm = (...args: string[]) => spawnSync(process.execPath, [tcmBin(), ...args], { cwd: dir, env: plainEnv(), encoding: "utf8" });
  tcm("init");
  fs.writeFileSync(path.join(dir, "scratch.ts"), `// tcm: namespace geo\nexport type Point = { x: number; y: number };\nexport const norm = (p: Point): number => Math.hypot(p.x, p.y);\n`);
  assert.equal(tcm("add").status, 0);
  const c = client(keep);
  await c.request("initialize", { processId: null, rootUri: pathToFileURL(dir).href, capabilities: {}, clientInfo: { name: "Visual Studio Code" } });
  c.notify("initialized", {});

  // `norm` isn't imported: importing it is offered, after the namespace line
  const file = path.join(dir, "scratch.ts");
  const uri = pathToFileURL(file).href;
  const unimported = `// tcm: namespace app\nexport const far = (p: { x: number; y: number }): boolean => norm(p) > 10;\n`;
  fs.writeFileSync(file, unimported);
  c.notify("textDocument/didOpen", { textDocument: { uri, languageId: "typescript", version: 1, text: unimported } });
  const fixes = await c.request("textDocument/codeAction", { textDocument: { uri }, range: { start: { line: 1, character: 62 }, end: { line: 1, character: 66 } }, context: { diagnostics: [] } });
  const fix = fixes.find((a: any) => a.title === `Import norm from "#codebase/geo"`);
  assert.ok(fix, JSON.stringify(fixes));
  assert.deepEqual(fix.edit.changes[uri], [{ range: { start: { line: 1, character: 0 }, end: { line: 1, character: 0 } }, newText: `import { norm } from "#codebase/geo";\n` }]);

  // with it imported, on the name: edit it here too, and it's added to the file
  const imported = `// tcm: namespace app\nimport { norm } from "#codebase/geo";\nexport const far = (p: { x: number; y: number }): boolean => norm(p) > 10;\n`;
  fs.writeFileSync(file, imported);
  c.notify("textDocument/didChange", { textDocument: { uri, version: 2 }, contentChanges: [{ text: imported }] });
  const actions = await c.request("textDocument/codeAction", { textDocument: { uri }, range: { start: { line: 2, character: 63 }, end: { line: 2, character: 63 } }, context: { diagnostics: [] } });
  const edit = actions.find((a: any) => a.title === "Edit geo.norm here too");
  assert.deepEqual(edit?.command, { title: "Edit geo.norm here too", command: "tcm.editThisToo", arguments: [uri, "geo.norm"] });
  assert.equal(await c.request("workspace/executeCommand", { command: "tcm.editInto", arguments: [uri, "geo.norm"] }), uri);
  const now = fs.readFileSync(file, "utf8");
  assert.match(now, /export const far = [\s\S]*\/\/ tcm: path geo\.norm\nexport const norm = \(p: Point\): number => Math\.hypot/);
  assert.match(now, /^import type \{ Point \} from "#codebase\/geo";$/m);
})

uses