import { expect } from "@std/expect"; import { describe, it } from "@std/testing/bdd"; import type { TransformationDiagnostic } from "../src/mod.ts"; import { COMMONFABRIC_TYPES } from "./commonfabric-test-types.ts"; import { parseModule, patternSchemas } from "./transformed-ast.ts"; import { transformSource } from "./utils.ts"; /** Fields inspected in the generated recursive object schema. */ interface Schema { /** Reference to a definition in the root schema. */ $ref?: string; /** Definitions used by recursive references. */ $defs?: Record; /** Schemas of the object's fields. */ properties?: Record; /** Policy claims attached to the value. */ ifc?: { writeAuthorizedBy?: unknown }; } describe("recursive writer schema", () => { it("warns without rejecting a generic recursion through a scope around a cell", async () => { const diagnostics: TransformationDiagnostic[] = []; await transformSource( `import { Cell, PerUser, pattern } from "commonfabric"; type Node = PerUser }>>; export default pattern<{ head: Node }>(() => ({}));`, { types: COMMONFABRIC_TYPES, typeCheck: true, pipelineDiagnostics: diagnostics, }, ); expect(diagnostics.filter(({ severity }) => severity === "error")).toEqual( [], ); expect(diagnostics.filter(({ type }) => type === "schema-type:unread")) .toHaveLength(1); }); it("reports the recursion limit for a CFC alias a pattern's result instantiates without end", async () => { const diagnostics: TransformationDiagnostic[] = []; await transformSource( `import { Confidential, pattern } from "commonfabric"; type Nest = Confidential<{ v: T; ref: unknown; inner?: Nest }, ["secret"]>; function nest(): Nest { return { v: "", ref: 1 }; } export default pattern(() => ({ nested: nest() }));`, { types: COMMONFABRIC_TYPES, typeCheck: true, pipelineDiagnostics: diagnostics, }, ); expect(diagnostics.map(({ type, severity }) => ({ type, severity }))) .toEqual([{ type: "cfc-schema:recursion-limit", severity: "error" }]); }); it("refuses a writer a number index signature holds beside a string one whose values recur without end", async () => { const diagnostics: TransformationDiagnostic[] = []; await transformSource( `import { Confidential, WriteAuthorizedBy, handler, pattern } from "commonfabric"; const save = handler(() => ({})); type Nest = Confidential<{ v: T; inner?: Nest }, ["secret"]>; interface In { [key: string]: Nest; [index: number]: WriteAuthorizedBy, typeof save>; } export default pattern(() => ({}));`, { types: COMMONFABRIC_TYPES, typeCheck: true, pipelineDiagnostics: diagnostics, }, ); expect( diagnostics.filter(({ type }) => type === "cfc-write-authorized-by:unread" ).map(({ severity }) => severity), ).toEqual(["error"]); }); for (const position of ["argument", "result"]) { for (const storedSource of [false, true]) { it(`rejects an indirect writer in a recursive ${position} from ${storedSource ? "stored" : "authored"} source`, async () => { const diagnostics: TransformationDiagnostic[] = []; await transformSource( `import { Confidential, WriteAuthorizedBy, handler, pattern } from "commonfabric"; const f = handler(() => {}); type Indirect = typeof f; type Pair = Confidential<{ left: WriteAuthorizedBy; right: WriteAuthorizedBy; }, readonly ["pair"]>; type Sec = Confidential<{ value: W; next?: Sec }, readonly ["a"]>; interface Holder { x: Sec>; y: Sec>; } export default ${ position === "argument" ? "pattern(() => ({}))" : "pattern<{}, Holder>(() => ({} as Holder))" };`, { types: COMMONFABRIC_TYPES, typeCheck: true, pipelineDiagnostics: diagnostics, storedSource, }, ); const unread = diagnostics.filter(({ type }) => type === "cfc-write-authorized-by:unread" ); expect(unread.length).toBeGreaterThan(0); for (const diagnostic of unread) { expect(diagnostic.severity).toBe("error"); expect(diagnostic.fileName).toBe("/test.tsx"); } }); } } it("emits each handler's write policy throughout its recursive input", async () => { const transformed = await transformSource( `import { Confidential, WriteAuthorizedBy, handler, pattern } from "commonfabric"; const f = handler(() => {}); const g: typeof f = handler(() => {}); type Identity = X; type Sec = Confidential<{ value: W; next?: Sec>; }, readonly ["a"]>; export default pattern<{ a: Sec>; b: Sec>; }>(() => ({}));`, { types: COMMONFABRIC_TYPES, typeCheck: true }, ); const { input } = patternSchemas(parseModule(transformed)) as { input: Schema; }; for (const [field, writer] of [["a", "f"], ["b", "g"]]) { let node = input.properties![field!]!; const visited = new Set(); for (;;) { expect(node.properties!.value!.ifc?.writeAuthorizedBy).toEqual({ __ctWriterIdentityOf: { file: "/test.tsx", path: [writer] }, }); const next = node.properties!.next!; if (next.$ref) { if (visited.has(next.$ref)) break; visited.add(next.$ref); node = input.$defs![next.$ref.split("/").pop()!]!; } else { node = next; } } expect(visited.size).toBeGreaterThan(0); } }); for (const operator of ["|", "&"]) { it(`emits a recursive reference when an alias repeats a writer with \`${operator}\``, async () => { const transformed = await transformSource( `import { Confidential, WriteAuthorizedBy, pattern } from "commonfabric"; declare const f: () => void; type Repeat = W ${operator} typeof f; type Sec = Confidential<{ value: WriteAuthorizedBy; next?: Sec>; }, readonly ["a"]>; export default pattern<{ root: Sec }>(() => ({}));`, { types: COMMONFABRIC_TYPES, typeCheck: true }, ); const { input } = patternSchemas(parseModule(transformed)) as { input: Schema; }; expect(Object.keys(input.$defs ?? {}).length).toBeGreaterThan(0); for (const definition of Object.values(input.$defs!)) { expect(definition.properties!.value!.ifc?.writeAuthorizedBy).toEqual({ __ctWriterIdentityOf: { file: "/test.tsx", path: ["f"] }, }); expect(definition.properties!.next!.$ref).toMatch(/^#\/\$defs\//); } }); } for ( const expression of [ "If>", "First<[W, WriteAuthorizedBy]>", "Unbox>", "NonNullable", ] ) { it(`reports an error when a recursive policy cannot settle through ${expression}`, async () => { const diagnostics: TransformationDiagnostic[] = []; await transformSource( `import { Confidential, WriteAuthorizedBy, handler, pattern } from "commonfabric"; const f = handler(() => {}); const g: typeof f = handler(() => {}); type If = C extends true ? A : B; type First = T[0]; type Box = { boxed: T }; type Unbox = T extends Box ? U : T; type Sec = Confidential<{ value: W; next?: Sec<${expression}>; }, readonly ["a"]>; export default pattern<{ a: Sec> }>(({ a }) => ({ a }));`, { types: COMMONFABRIC_TYPES, typeCheck: true, pipelineDiagnostics: diagnostics, }, ); const recursionLimits = diagnostics.filter(({ type }) => type === "cfc-schema:recursion-limit" ); expect(recursionLimits.length).toBeGreaterThan(0); for (const diagnostic of recursionLimits) { expect(diagnostic.severity).toBe("error"); expect(diagnostic.fileName).toBe("/test.tsx"); } }); } });