Format authored monorepo code with pinned language formatters
This commit is contained in:
+51
-14
@@ -3,15 +3,30 @@ 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";
|
||||
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"],
|
||||
]);
|
||||
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');
|
||||
@@ -20,8 +35,18 @@ test("lossless tokens, UTF-16 ranges, and safe source edits", () => {
|
||||
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.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", () => {
|
||||
@@ -36,10 +61,12 @@ test("imports preserve root text, deduplicate diamonds, reject cycles, and compi
|
||||
const root = addWorkspaceImport(addWorkspaceImport(workspace, "a.qx"), "b.qx");
|
||||
assert.equal(addWorkspaceImport(root, "a.qx"), root);
|
||||
assert.match(root, /keep 🐈 comment/);
|
||||
const files: Record<string, string> = { "workspace.qx": root,
|
||||
const files: Record<string, string> = {
|
||||
"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"; }' };
|
||||
"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);
|
||||
@@ -49,15 +76,23 @@ test("imports preserve root text, deduplicate diamonds, reject cycles, and compi
|
||||
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/);
|
||||
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<never> => { throw new Error("unexpected resource"); };
|
||||
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<never> => {
|
||||
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);
|
||||
@@ -72,8 +107,10 @@ test("repository scaffolding validates before writing and refuses duplicates and
|
||||
await assert.rejects(readQxSource(root, "link.qx"), /ordinary files/);
|
||||
});
|
||||
test("lowering diagnostics map to the imported file", async () => {
|
||||
const files: Record<string, string> = { "workspace.qx": addWorkspaceImport(workspace, "bad.qx"),
|
||||
"bad.qx": 'fragment {\n conform Missing as Nope {}\n}' };
|
||||
const files: Record<string, string> = {
|
||||
"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);
|
||||
|
||||
Reference in New Issue
Block a user