testImportModuleAugmentation

tcmts.cli.test.e2e.import.testImportModuleAugmentation · #lwub45vc · term

A module augmentation (declare module "./schema" { namespace Annotations … }) is a definition of its own (<file>.augments) that every check includes: code passing what it adds typechecks without importing it, its module is written to merge with the definition wherever that's rendered, and it's edited from a scratch file naming the definition's namespace (declare module "#codebase/lib/schema").

import { okIn, tempDir } from "#codebase/tcmts/cli/test/e2e/harness";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";

export const testImportModuleAugmentation = () => {
  const dir = tempDir("augment module");
  const lib = tempDir("augment module lib");
  const tcm = (...args: string[]) => okIn(dir, ...args);
  tcm("init");
  const write = (file: string, text: string) => {
    fs.mkdirSync(path.dirname(path.join(lib, file)), { recursive: true });
    fs.writeFileSync(path.join(lib, file), text);
  };
  write(
    "tsconfig.json",
    JSON.stringify({ compilerOptions: { strict: true, lib: ["ESNext"], target: "ES2022" } }),
  );
  write(
    "src/schema.ts",
    `export declare namespace Annotations {
  interface Augment {
    readonly title?: string;
  }
}
export const annotate = (a: Annotations.Augment): Annotations.Augment => a;
`,
  );
  write(
    "src/http.ts",
    `import type { Annotations } from "./schema";

declare module "./schema" {
  namespace Annotations {
    interface Augment {
      readonly status?: number;
    }
  }
}

export const statusOf = (a: Annotations.Augment): number => a.status ?? 200;
`,
  );
  // passes what the augmentation adds without importing the file it's in
  write(
    "src/errors.ts",
    `import { annotate } from "./schema";\nexport const notFound = annotate({ title: "Not Found", status: 404 });\n`,
  );
  tcm("import", path.join(lib, "src"), "--into", "lib");
  assert.match(
    tcm("view", "lib.http.augments"),
    /declare module "#codebase\/lib\/schema" \{\n\s+namespace Annotations \{/,
  );
  assert.match(tcm("verify"), /verified \d+ definitions/);

  const scratch = path.join(dir, "scratch.ts");
  fs.writeFileSync(
    scratch,
    `// tcm: namespace app
import { notFound } from "#codebase/lib/errors";
import { statusOf } from "#codebase/lib/http";

export function main() {
  console.log("status", statusOf(notFound), notFound.title);
}
`,
  );
  tcm("add");
  assert.match(tcm("run", "app.main"), /status 404 Not Found/);

  // edited from a scratch file, checked without the stored version, and what it adds reaches every check
  tcm("edit", "lib.http.augments");
  fs.writeFileSync(
    scratch,
    fs
      .readFileSync(scratch, "utf8")
      .replace(
        "readonly status?: number;",
        "readonly status?: number;\n      readonly retryAfter?: number;",
      ),
  );
  tcm("update");
  fs.writeFileSync(
    scratch,
    `// tcm: namespace app\nimport { annotate } from "#codebase/lib/schema";\nexport const busy = () => annotate({ status: 503, retryAfter: 30 }).retryAfter;\n`,
  );
  tcm("add");
  assert.match(tcm("run", "app.busy"), /30/);
}

uses