import assert from "node:assert/strict"; import test from "node:test"; import { mkdtemp, writeFile, readFile, rm, symlink } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { parseQx, formatQx, lintQx, applySourceEdits, addWorkspaceImport, walkSyntax, resolveQxSources, readQxSource, compileCapabilitySource, scaffoldAtom, compileWorkspaceRepository, } from "../src/capability-language/index.js"; const workspace = `workspace Test id "w" revision "w@1" commit "${"1".repeat(40)}" {\n// keep 🐈 comment\n}\n`; test("lint reports invalid and redundant imports without resolving repositories", () => { const source = workspace.replace("}\n", 'import "a.qx"; import "a.qx"; import "../bad.qx";\n}\n'); assert.deepEqual( lintQx(source).map((entry) => [entry.code, entry.severity]), [ ["duplicate-source-import", "warning"], ["invalid-source-import", "error"], ], ); }); test("lossless tokens, UTF-16 ranges, and safe source edits", () => { const text = workspace.replace("}\n", 'atom Cat id "🐈";\n}\n'); const parsed = parseQx(text); assert.deepEqual(parsed.diagnostics, []); assert.equal(parsed.tokens.map((token) => token.text).join(""), text); const atom = [...walkSyntax(parsed.root)].find((node) => node.kind === "atomDecl")!; assert.equal(text.slice(atom.start, atom.end), 'atom Cat id "🐈";'); assert.equal( applySourceEdits(text, [{ ...atom, text: 'atom Dog id "dog";' }]), text.replace('atom Cat id "🐈";', 'atom Dog id "dog";'), ); assert.throws( () => applySourceEdits(text, [ { start: 0, end: 4, text: "" }, { start: 3, end: 7, text: "" }, ]), /overlapping/, ); assert.throws(() => applySourceEdits(text, [{ start: -1, end: 0, text: "" }]), /Invalid/); }); test("formatting preserves comments and strings, is idempotent, and rejects invalid source", () => { const text = workspace.replace("}\n", 'atom Cat id "{ cat }"; /* multiline\n unchanged */\n}\n'); const formatted = formatQx(text); assert.match(formatted, / atom Cat id "\{ cat \}"; \/\* multiline\n unchanged \*\//); assert.equal(formatQx(formatted), formatted); assert.deepEqual(parseQx(formatted).diagnostics, []); assert.throws(() => formatQx("workspace {"), /syntax errors/); }); test("imports preserve root text, deduplicate diamonds, reject cycles, and compile together", async () => { const root = addWorkspaceImport(addWorkspaceImport(workspace, "a.qx"), "b.qx"); assert.equal(addWorkspaceImport(root, "a.qx"), root); assert.match(root, /keep 🐈 comment/); const files: Record = { "workspace.qx": root, "a.qx": 'fragment { import "shared.qx"; atom A id "a"; }', "b.qx": 'fragment { import "shared.qx"; atom B id "b"; }', "shared.qx": 'fragment { atom Shared id "shared"; }', }; const result = await resolveQxSources(async (name) => files[name]!); assert.equal(result.sourceFiles.length, 4); const compiled = compileCapabilitySource(result.source); assert.equal(compiled.ok, true, JSON.stringify(compiled.diagnostics)); if (compiled.ok) assert.equal(compiled.workspace.atoms.length, 3); const unresolved = compileCapabilitySource(root); assert.equal(unresolved.ok, false); assert.match(JSON.stringify(unresolved.diagnostics), /unresolved-source-import/); files["shared.qx"] = 'fragment { import "a.qx"; }'; await assert.rejects( resolveQxSources(async (name) => files[name]!), /cycle/, ); assert.throws(() => addWorkspaceImport(workspace, "../x.qx"), /Invalid/); }); test("repository scaffolding validates before writing and refuses duplicates and symlinks", async (t) => { const root = await mkdtemp(path.join(os.tmpdir(), "qx-scaffold-")); t.after(() => rm(root, { recursive: true, force: true })); await writeFile(path.join(root, "workspace.qx"), workspace); await writeFile( path.join(root, "quixos.lock"), `quixos-lock version 1 { quixos source { repository "https://example.test/q.git"; commit "${"1".repeat(40)}"; } }`, ); const resolveResource = async (): Promise => { throw new Error("unexpected resource"); }; const options = { root, name: "Cat", id: "cat", write: false, resolveResource }; await scaffoldAtom(options); assert.equal(await readFile(path.join(root, "workspace.qx"), "utf8"), workspace); await assert.rejects(readFile(path.join(root, "Cat.qx")), /ENOENT/); await scaffoldAtom({ ...options, write: true }); const compiled = await compileWorkspaceRepository({ rootDirectory: root, resolveResource }); assert.equal(compiled.workspace.atoms[0]!.id, "cat"); await assert.rejects(scaffoldAtom({ ...options, write: true }), /already exists/); await assert.rejects(scaffoldAtom({ ...options, name: "Other", write: true }), /Duplicate|duplicate/); await assert.rejects(readFile(path.join(root, "Other.qx")), /ENOENT/); await symlink(path.join(root, "Cat.qx"), path.join(root, "link.qx")); await assert.rejects(readQxSource(root, "link.qx"), /ordinary files/); }); test("lowering diagnostics map to the imported file", async () => { const files: Record = { "workspace.qx": addWorkspaceImport(workspace, "bad.qx"), "bad.qx": "fragment {\n conform Missing as Nope {}\n}", }; const sources = await resolveQxSources(async (name) => files[name]!); const compiled = compileCapabilitySource(sources.source); assert.equal(compiled.ok, false); const diagnostic = compiled.diagnostics[0]!; const position = sources.originalPosition(diagnostic.line, diagnostic.column); assert.equal(position.fileName, "bad.qx"); assert.equal(position.line, 2); });